[
  {
    "path": ".dockerignore",
    "content": "node_modules\nplaywright-report\ncoverage\ndist\ntest-results\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es2020\": true,\n    \"jest\": true,\n    \"node\": true\n  },\n  \"settings\": {\n    \"react\": {\n      \"version\": \"detect\"\n    }\n  },\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:react/recommended\",\n    \"plugin:@typescript-eslint/eslint-recommended\",\n    \"plugin:@typescript-eslint/recommended\",\n    \"plugin:prettier/recommended\",\n    \"plugin:tailwindcss/recommended\"\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"ecmaFeatures\": {\n      \"jsx\": true\n    },\n    \"ecmaVersion\": 11,\n    \"sourceType\": \"module\"\n  },\n  \"plugins\": [\n    \"react\",\n    \"react-hooks\",\n    \"@typescript-eslint\",\n    \"tailwindcss\"\n  ],\n  \"rules\": {\n    \"react-hooks/rules-of-hooks\": \"error\",\n    \"react-hooks/exhaustive-deps\": \"warn\",\n    \"react/prop-types\": \"off\",\n    \"react/react-in-jsx-scope\": \"off\",\n    \"@typescript-eslint/explicit-module-boundary-types\": \"off\",\n    \"@typescript-eslint/no-non-null-assertion\": \"off\",\n    \"tailwindcss/classnames-order\": \"warn\",\n    \"tailwindcss/no-custom-classname\": \"warn\",\n    \"tailwindcss/no-contradicting-classname\": \"error\",\n    \"@typescript-eslint/ban-types\": \"off\",\n    \"@typescript-eslint/ban-ts-comment\": \"off\",\n    \"@typescript-eslint/no-explicit-any\": \"off\"\n  }\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [iib0011]\nbuy_me_a_coffee: iib0011"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  push:\n    branches:\n      - main\n    tags:\n      - 'v*'\n  pull_request:\n    branches:\n      - main\njobs:\n  test-and-build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v3\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: '20'\n      - name: Install dependencies\n        run: npm install\n      - name: Run tests\n        run: npm run test\n      - name: Build project\n        run: npm run build\n  e2e-test:\n    name: 'Playwright Tests'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n      - name: Install dependencies\n        run: npm ci\n      - name: Install Playwright Browsers\n        run: npx playwright install --with-deps\n      - name: Run Playwright tests\n        run: npx playwright test\n      - uses: actions/upload-artifact@v4\n        if: ${{ !cancelled() }}\n        with:\n          name: playwright-report\n          path: playwright-report/\n          retention-days: 30\n  build-and-push-docker:\n    name: Build and Push Multi-Platform Docker Image\n    runs-on: ubuntu-latest\n    needs:\n      - test-and-build\n      - e2e-test\n    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v3\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v2\n        with:\n          platforms: 'arm64,amd64'\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v2\n      - name: Login to DockerHub\n        uses: docker/login-action@v2\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n      - name: Extract metadata for Docker\n        id: meta\n        uses: docker/metadata-action@v4\n        with:\n          images: iib0011/omni-tools\n          tags: |\n            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}\n            type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}\n            type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}\n      - name: Build and push Docker image\n        uses: docker/build-push-action@v4\n        with:\n          context: .\n          platforms: linux/amd64,linux/arm64\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n          cache-from: type=gha\n          cache-to: type=gha,mode=max\n  deploy:\n    if: github.ref == 'refs/heads/main'\n    needs:\n      - test-and-build\n      - e2e-test\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v3\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: '20'\n      - name: Install dependencies\n        run: npm install\n      - name: Build project\n        run: npm run build\n      - name: Deploy to Netlify\n        uses: nwtgck/actions-netlify@v1.2\n        with:\n          publish-dir: ./dist\n          production-branch: main\n          deploy-message: ${{ github.event.head_commit.message }}\n          enable-pull-request-comment: true\n          enable-commit-comment: true\n          overwrites-pull-request-comment: true\n        env:\n          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}\n          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}\n        timeout-minutes: 20\n"
  },
  {
    "path": ".gitignore",
    "content": "dist\ndist-ssr\n*.local\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# vercel\n.vercel\n\n/test-results\n/playwright-report\n\ndist.zip\n.aider*\n.qodo\n\nerror.txt\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "npx lint-staged"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <HTMLCodeStyleSettings>\n      <option name=\"HTML_SPACE_INSIDE_EMPTY_TAG\" value=\"true\" />\n    </HTMLCodeStyleSettings>\n    <JSCodeStyleSettings version=\"0\">\n      <option name=\"FORCE_SEMICOLON_STYLE\" value=\"true\" />\n      <option name=\"SPACE_BEFORE_FUNCTION_LEFT_PARENTH\" value=\"false\" />\n      <option name=\"USE_DOUBLE_QUOTES\" value=\"false\" />\n      <option name=\"FORCE_QUOTE_STYlE\" value=\"true\" />\n      <option name=\"ENFORCE_TRAILING_COMMA\" value=\"Remove\" />\n      <option name=\"SPACES_WITHIN_OBJECT_LITERAL_BRACES\" value=\"true\" />\n      <option name=\"SPACES_WITHIN_IMPORTS\" value=\"true\" />\n    </JSCodeStyleSettings>\n    <TypeScriptCodeStyleSettings version=\"0\">\n      <option name=\"FORCE_SEMICOLON_STYLE\" value=\"true\" />\n      <option name=\"SPACE_BEFORE_FUNCTION_LEFT_PARENTH\" value=\"false\" />\n      <option name=\"USE_DOUBLE_QUOTES\" value=\"false\" />\n      <option name=\"FORCE_QUOTE_STYlE\" value=\"true\" />\n      <option name=\"ENFORCE_TRAILING_COMMA\" value=\"Remove\" />\n      <option name=\"SPACES_WITHIN_OBJECT_LITERAL_BRACES\" value=\"true\" />\n      <option name=\"SPACES_WITHIN_IMPORTS\" value=\"true\" />\n    </TypeScriptCodeStyleSettings>\n    <VueCodeStyleSettings>\n      <option name=\"INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER\" value=\"false\" />\n      <option name=\"INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER\" value=\"false\" />\n    </VueCodeStyleSettings>\n    <codeStyleSettings language=\"HTML\">\n      <option name=\"SOFT_MARGINS\" value=\"80\" />\n      <indentOptions>\n        <option name=\"INDENT_SIZE\" value=\"2\" />\n        <option name=\"CONTINUATION_INDENT_SIZE\" value=\"2\" />\n        <option name=\"TAB_SIZE\" value=\"2\" />\n      </indentOptions>\n    </codeStyleSettings>\n    <codeStyleSettings language=\"JavaScript\">\n      <option name=\"SOFT_MARGINS\" value=\"80\" />\n      <indentOptions>\n        <option name=\"INDENT_SIZE\" value=\"2\" />\n        <option name=\"CONTINUATION_INDENT_SIZE\" value=\"2\" />\n        <option name=\"TAB_SIZE\" value=\"2\" />\n      </indentOptions>\n    </codeStyleSettings>\n    <codeStyleSettings language=\"TypeScript\">\n      <option name=\"SOFT_MARGINS\" value=\"80\" />\n      <indentOptions>\n        <option name=\"INDENT_SIZE\" value=\"2\" />\n        <option name=\"CONTINUATION_INDENT_SIZE\" value=\"2\" />\n        <option name=\"TAB_SIZE\" value=\"2\" />\n      </indentOptions>\n    </codeStyleSettings>\n    <codeStyleSettings language=\"Vue\">\n      <option name=\"SOFT_MARGINS\" value=\"80\" />\n      <indentOptions>\n        <option name=\"CONTINUATION_INDENT_SIZE\" value=\"2\" />\n      </indentOptions>\n    </codeStyleSettings>\n  </code_scheme>\n</component>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n  </state>\n</component>"
  },
  {
    "path": ".idea/inspectionProfiles/Project_Default.xml",
    "content": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project Default\" />\n    <inspection_tool class=\"Eslint\" enabled=\"true\" level=\"WARNING\" enabled_by_default=\"true\" />\n    <inspection_tool class=\"ImplicitTypeConversion\" enabled=\"true\" level=\"WARNING\" enabled_by_default=\"true\">\n      <option name=\"BITS\" value=\"1720\" />\n      <option name=\"FLAG_EXPLICIT_CONVERSION\" value=\"true\" />\n      <option name=\"IGNORE_NODESET_TO_BOOLEAN_VIA_STRING\" value=\"true\" />\n    </inspection_tool>\n  </profile>\n</component>"
  },
  {
    "path": ".idea/modules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n      <module fileurl=\"file://$PROJECT_DIR$/.idea/omni-tools.iml\" filepath=\"$PROJECT_DIR$/.idea/omni-tools.iml\" />\n    </modules>\n  </component>\n</project>"
  },
  {
    "path": ".idea/omni-tools.iml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"JAVA_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\" inherit-compiler-output=\"true\">\n    <exclude-output />\n    <content url=\"file://$MODULE_DIR$\">\n      <excludeFolder url=\"file://$MODULE_DIR$/dist\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/test-results\" />\n    </content>\n    <orderEntry type=\"inheritedJdk\" />\n    <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n  </component>\n</module>"
  },
  {
    "path": ".idea/prettier.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"PrettierConfiguration\">\n    <option name=\"myConfigurationMode\" value=\"AUTOMATIC\" />\n    <option name=\"myRunOnSave\" value=\"true\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/shelf/Uncommitted_changes_before_Checkout_at_2_27_2025_11_44_AM_[Changes]/shelved.patch",
    "content": "Index: .idea/workspace.xml\nIDEA additional info:\nSubsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP\n<+><?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\r\\n<project version=\\\"4\\\">\\r\\n  <component name=\\\"AutoImportSettings\\\">\\r\\n    <option name=\\\"autoReloadType\\\" value=\\\"SELECTIVE\\\" />\\r\\n  </component>\\r\\n  <component name=\\\"ChangeListManager\\\">\\r\\n    <list default=\\\"true\\\" id=\\\"b30e2810-c4c1-4aad-b134-794e52cc1c7d\\\" name=\\\"Changes\\\" comment=\\\"refact: examples\\\">\\r\\n      <change beforePath=\\\"$PROJECT_DIR$/.idea/workspace.xml\\\" beforeDir=\\\"false\\\" afterPath=\\\"$PROJECT_DIR$/.idea/workspace.xml\\\" afterDir=\\\"false\\\" />\\r\\n      <change beforePath=\\\"$PROJECT_DIR$/src/components/ToolHeader.tsx\\\" beforeDir=\\\"false\\\" afterPath=\\\"$PROJECT_DIR$/src/components/ToolHeader.tsx\\\" afterDir=\\\"false\\\" />\\r\\n      <change beforePath=\\\"$PROJECT_DIR$/src/components/examples/Examples.tsx\\\" beforeDir=\\\"false\\\" afterPath=\\\"$PROJECT_DIR$/src/components/examples/ToolExamples.tsx\\\" afterDir=\\\"false\\\" />\\r\\n      <change beforePath=\\\"$PROJECT_DIR$/src/pages/tools/string/join/index.tsx\\\" beforeDir=\\\"false\\\" afterPath=\\\"$PROJECT_DIR$/src/pages/tools/string/join/index.tsx\\\" afterDir=\\\"false\\\" />\\r\\n      <change beforePath=\\\"$PROJECT_DIR$/src/pages/tools/string/split/index.tsx\\\" beforeDir=\\\"false\\\" afterPath=\\\"$PROJECT_DIR$/src/pages/tools/string/split/index.tsx\\\" afterDir=\\\"false\\\" />\\r\\n      <change beforePath=\\\"$PROJECT_DIR$/src/tools/defineTool.tsx\\\" beforeDir=\\\"false\\\" afterPath=\\\"$PROJECT_DIR$/src/tools/defineTool.tsx\\\" afterDir=\\\"false\\\" />\\r\\n    </list>\\r\\n    <option name=\\\"SHOW_DIALOG\\\" value=\\\"false\\\" />\\r\\n    <option name=\\\"HIGHLIGHT_CONFLICTS\\\" value=\\\"true\\\" />\\r\\n    <option name=\\\"HIGHLIGHT_NON_ACTIVE_CHANGELIST\\\" value=\\\"false\\\" />\\r\\n    <option name=\\\"LAST_RESOLUTION\\\" value=\\\"IGNORE\\\" />\\r\\n  </component>\\r\\n  <component name=\\\"FormatOnSaveOptions\\\">\\r\\n    <option name=\\\"myRunOnSave\\\" value=\\\"true\\\" />\\r\\n  </component>\\r\\n  <component name=\\\"Git.Merge.Settings\\\">\\r\\n    <option name=\\\"BRANCH\\\" value=\\\"origin/main\\\" />\\r\\n  </component>\\r\\n  <component name=\\\"Git.Settings\\\">\\r\\n    <option name=\\\"RECENT_BRANCH_BY_REPOSITORY\\\">\\r\\n      <map>\\r\\n        <entry key=\\\"$PROJECT_DIR$\\\" value=\\\"main\\\" />\\r\\n      </map>\\r\\n    </option>\\r\\n    <option name=\\\"RECENT_GIT_ROOT_PATH\\\" value=\\\"$PROJECT_DIR$\\\" />\\r\\n    <option name=\\\"RESET_MODE\\\" value=\\\"HARD\\\" />\\r\\n  </component>\\r\\n  <component name=\\\"GitHubPullRequestSearchHistory\\\">{\\r\\n  &quot;lastFilter&quot;: {\\r\\n    &quot;state&quot;: &quot;OPEN&quot;,\\r\\n    &quot;assignee&quot;: &quot;iib0011&quot;\\r\\n  }\\r\\n}</component>\\r\\n  <component name=\\\"GithubPullRequestsUISettings\\\">{\\r\\n  &quot;selectedUrlAndAccountId&quot;: {\\r\\n    &quot;url&quot;: &quot;https://github.com/iib0011/omni-tools.git&quot;,\\r\\n    &quot;accountId&quot;: &quot;45f8cd51-000f-4ba4-a4c6-c4d96ac9b1e5&quot;\\r\\n  }\\r\\n}</component>\\r\\n  <component name=\\\"KubernetesApiProvider\\\">{\\r\\n  &quot;isMigrated&quot;: true\\r\\n}</component>\\r\\n  <component name=\\\"MarkdownSettingsMigration\\\">\\r\\n    <option name=\\\"stateVersion\\\" value=\\\"1\\\" />\\r\\n  </component>\\r\\n  <component name=\\\"ProjectColorInfo\\\">{\\r\\n  &quot;associatedIndex&quot;: 0\\r\\n}</component>\\r\\n  <component name=\\\"ProjectId\\\" id=\\\"2i6g3WkUxdCURKYvUIJ9LMY5Qsc\\\" />\\r\\n  <component name=\\\"ProjectLevelVcsManager\\\" settingsEditedManually=\\\"true\\\">\\r\\n    <ConfirmationsSetting value=\\\"2\\\" id=\\\"Add\\\" />\\r\\n  </component>\\r\\n  <component name=\\\"ProjectViewState\\\">\\r\\n    <option name=\\\"autoscrollFromSource\\\" value=\\\"true\\\" />\\r\\n    <option name=\\\"hideEmptyMiddlePackages\\\" value=\\\"true\\\" />\\r\\n    <option name=\\\"showLibraryContents\\\" value=\\\"true\\\" />\\r\\n  </component>\\r\\n  <component name=\\\"PropertiesComponent\\\"><![CDATA[{\\r\\n  \\\"keyToString\\\": {\\r\\n    \\\"ASKED_ADD_EXTERNAL_FILES\\\": \\\"true\\\",\\r\\n    \\\"ASKED_SHARE_PROJECT_CONFIGURATION_FILES\\\": \\\"true\\\",\\r\\n    \\\"Docker.Dockerfile build.executor\\\": \\\"Run\\\",\\r\\n    \\\"Docker.Dockerfile.executor\\\": \\\"Run\\\",\\r\\n    \\\"Playwright.JoinText Component.executor\\\": \\\"Run\\\",\\r\\n    \\\"Playwright.JoinText Component.should merge text pieces with specified join character.executor\\\": \\\"Run\\\",\\r\\n    \\\"RunOnceActivity.OpenProjectViewOnStart\\\": \\\"true\\\",\\r\\n    \\\"RunOnceActivity.ShowReadmeOnStart\\\": \\\"true\\\",\\r\\n    \\\"RunOnceActivity.git.unshallow\\\": \\\"true\\\",\\r\\n    \\\"Vitest.compute function (1).executor\\\": \\\"Run\\\",\\r\\n    \\\"Vitest.compute function.executor\\\": \\\"Run\\\",\\r\\n    \\\"Vitest.mergeText.executor\\\": \\\"Run\\\",\\r\\n    \\\"Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor\\\": \\\"Run\\\",\\r\\n    \\\"Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor\\\": \\\"Run\\\",\\r\\n    \\\"git-widget-placeholder\\\": \\\"examples\\\",\\r\\n    \\\"ignore.virus.scanning.warn.message\\\": \\\"true\\\",\\r\\n    \\\"kotlin-language-version-configured\\\": \\\"true\\\",\\r\\n    \\\"last_opened_file_path\\\": \\\"C:/Users/Ibrahima/IdeaProjects/omni-tools/src/assets\\\",\\r\\n    \\\"node.js.detected.package.eslint\\\": \\\"true\\\",\\r\\n    \\\"node.js.detected.package.tslint\\\": \\\"true\\\",\\r\\n    \\\"node.js.selected.package.eslint\\\": \\\"(autodetect)\\\",\\r\\n    \\\"node.js.selected.package.tslint\\\": \\\"(autodetect)\\\",\\r\\n    \\\"nodejs_package_manager_path\\\": \\\"npm\\\",\\r\\n    \\\"npm.dev.executor\\\": \\\"Run\\\",\\r\\n    \\\"npm.lint.executor\\\": \\\"Run\\\",\\r\\n    \\\"npm.prebuild.executor\\\": \\\"Run\\\",\\r\\n    \\\"npm.script:create:tool.executor\\\": \\\"Run\\\",\\r\\n    \\\"npm.test.executor\\\": \\\"Run\\\",\\r\\n    \\\"npm.test:e2e.executor\\\": \\\"Run\\\",\\r\\n    \\\"npm.test:e2e:run.executor\\\": \\\"Run\\\",\\r\\n    \\\"prettierjs.PrettierConfiguration.Package\\\": \\\"C:\\\\\\\\Users\\\\\\\\Ibrahima\\\\\\\\IdeaProjects\\\\\\\\omni-tools\\\\\\\\node_modules\\\\\\\\prettier\\\",\\r\\n    \\\"project.structure.last.edited\\\": \\\"Problems\\\",\\r\\n    \\\"project.structure.proportion\\\": \\\"0.0\\\",\\r\\n    \\\"project.structure.side.proportion\\\": \\\"0.2\\\",\\r\\n    \\\"settings.editor.selected.configurable\\\": \\\"settings.typescriptcompiler\\\",\\r\\n    \\\"ts.external.directory.path\\\": \\\"C:\\\\\\\\Users\\\\\\\\Ibrahima\\\\\\\\IdeaProjects\\\\\\\\omni-tools\\\\\\\\node_modules\\\\\\\\typescript\\\\\\\\lib\\\",\\r\\n    \\\"vue.rearranger.settings.migration\\\": \\\"true\\\"\\r\\n  }\\r\\n}]]></component>\\r\\n  <component name=\\\"ReactDesignerToolWindowState\\\">\\r\\n    <option name=\\\"myId2Visible\\\">\\r\\n      <map>\\r\\n        <entry key=\\\"com.intellij.reactbuddy.reactComponents\\\" value=\\\"false\\\" />\\r\\n        <entry key=\\\"com.intellij.reactbuddy.reactInspector\\\" value=\\\"false\\\" />\\r\\n        <entry key=\\\"com.intellij.reactbuddy.storybook\\\" value=\\\"false\\\" />\\r\\n      </map>\\r\\n    </option>\\r\\n  </component>\\r\\n  <component name=\\\"RecentsManager\\\">\\r\\n    <key name=\\\"CopyFile.RECENT_KEYS\\\">\\r\\n      <recent name=\\\"C:\\\\Users\\\\Ibrahima\\\\IdeaProjects\\\\omni-tools\\\\src\\\\assets\\\" />\\r\\n      <recent name=\\\"C:\\\\Users\\\\Ibrahima\\\\IdeaProjects\\\\omni-tools\\\\.github\\\" />\\r\\n      <recent name=\\\"C:\\\\Users\\\\HP\\\\IdeaProjects\\\\omni-tools\\\\src\\\\components\\\\options\\\" />\\r\\n      <recent name=\\\"C:\\\\Users\\\\HP\\\\IdeaProjects\\\\omni-tools\\\\src\\\\assets\\\" />\\r\\n      <recent name=\\\"C:\\\\Users\\\\HP\\\\IdeaProjects\\\\omni-tools\\\\src\\\\pages\\\\string\\\" />\\r\\n    </key>\\r\\n    <key name=\\\"MoveFile.RECENT_KEYS\\\">\\r\\n      <recent name=\\\"C:\\\\Users\\\\Ibrahima\\\\IdeaProjects\\\\omni-tools\\\\public\\\\assets\\\" />\\r\\n      <recent name=\\\"C:\\\\Users\\\\Ibrahima\\\\IdeaProjects\\\\omni-tools\\\\src\\\\pages\\\\tools\\\" />\\r\\n      <recent name=\\\"C:\\\\Users\\\\Ibrahima\\\\IdeaProjects\\\\omni-tools\\\\src\\\\pages\\\\categories\\\" />\\r\\n      <recent name=\\\"C:\\\\Users\\\\HP\\\\IdeaProjects\\\\omni-tools\\\\src\\\\components\\\" />\\r\\n      <recent name=\\\"C:\\\\Users\\\\HP\\\\IdeaProjects\\\\omni-tools\\\\src\\\\components\\\\options\\\" />\\r\\n    </key>\\r\\n  </component>\\r\\n  <component name=\\\"RunManager\\\" selected=\\\"npm.dev\\\">\\r\\n    <configuration name=\\\"Dockerfile build\\\" type=\\\"docker-deploy\\\" factoryName=\\\"dockerfile\\\" temporary=\\\"true\\\" server-name=\\\"Docker\\\">\\r\\n      <deployment type=\\\"dockerfile\\\">\\r\\n        <settings>\\r\\n          <option name=\\\"imageTag\\\" value=\\\"omnitools\\\" />\\r\\n          <option name=\\\"buildCliOptions\\\" value=\\\"--target build\\\" />\\r\\n          <option name=\\\"containerName\\\" value=\\\"omni-tools\\\" />\\r\\n          <option name=\\\"publishAllPorts\\\" value=\\\"true\\\" />\\r\\n          <option name=\\\"sourceFilePath\\\" value=\\\"Dockerfile\\\" />\\r\\n        </settings>\\r\\n      </deployment>\\r\\n      <method v=\\\"2\\\" />\\r\\n    </configuration>\\r\\n    <configuration name=\\\"Dockerfile\\\" type=\\\"docker-deploy\\\" factoryName=\\\"dockerfile\\\" temporary=\\\"true\\\" server-name=\\\"Docker\\\">\\r\\n      <deployment type=\\\"dockerfile\\\">\\r\\n        <settings>\\r\\n          <option name=\\\"sourceFilePath\\\" value=\\\"Dockerfile\\\" />\\r\\n        </settings>\\r\\n      </deployment>\\r\\n      <method v=\\\"2\\\" />\\r\\n    </configuration>\\r\\n    <configuration default=\\\"true\\\" type=\\\"docker-deploy\\\" factoryName=\\\"dockerfile\\\" temporary=\\\"true\\\">\\r\\n      <deployment type=\\\"dockerfile\\\">\\r\\n        <settings />\\r\\n      </deployment>\\r\\n      <method v=\\\"2\\\" />\\r\\n    </configuration>\\r\\n    <configuration name=\\\"dev\\\" type=\\\"js.build_tools.npm\\\" temporary=\\\"true\\\" nameIsGenerated=\\\"true\\\">\\r\\n      <package-json value=\\\"$PROJECT_DIR$/package.json\\\" />\\r\\n      <command value=\\\"run\\\" />\\r\\n      <scripts>\\r\\n        <script value=\\\"dev\\\" />\\r\\n      </scripts>\\r\\n      <node-interpreter value=\\\"project\\\" />\\r\\n      <envs />\\r\\n      <method v=\\\"2\\\" />\\r\\n    </configuration>\\r\\n    <configuration name=\\\"lint\\\" type=\\\"js.build_tools.npm\\\" temporary=\\\"true\\\" nameIsGenerated=\\\"true\\\">\\r\\n      <package-json value=\\\"$PROJECT_DIR$/package.json\\\" />\\r\\n      <command value=\\\"run\\\" />\\r\\n      <scripts>\\r\\n        <script value=\\\"lint\\\" />\\r\\n      </scripts>\\r\\n      <node-interpreter value=\\\"project\\\" />\\r\\n      <envs />\\r\\n      <method v=\\\"2\\\" />\\r\\n    </configuration>\\r\\n    <configuration name=\\\"test\\\" type=\\\"js.build_tools.npm\\\" temporary=\\\"true\\\" nameIsGenerated=\\\"true\\\">\\r\\n      <package-json value=\\\"$PROJECT_DIR$/package.json\\\" />\\r\\n      <command value=\\\"run\\\" />\\r\\n      <scripts>\\r\\n        <script value=\\\"test\\\" />\\r\\n      </scripts>\\r\\n      <node-interpreter value=\\\"project\\\" />\\r\\n      <envs />\\r\\n      <method v=\\\"2\\\" />\\r\\n    </configuration>\\r\\n    <list>\\r\\n      <item itemvalue=\\\"Docker.Dockerfile\\\" />\\r\\n      <item itemvalue=\\\"Docker.Dockerfile build\\\" />\\r\\n      <item itemvalue=\\\"npm.test\\\" />\\r\\n      <item itemvalue=\\\"npm.dev\\\" />\\r\\n      <item itemvalue=\\\"npm.lint\\\" />\\r\\n    </list>\\r\\n    <recent_temporary>\\r\\n      <list>\\r\\n        <item itemvalue=\\\"npm.dev\\\" />\\r\\n        <item itemvalue=\\\"Docker.Dockerfile\\\" />\\r\\n        <item itemvalue=\\\"npm.test\\\" />\\r\\n        <item itemvalue=\\\"npm.lint\\\" />\\r\\n        <item itemvalue=\\\"Docker.Dockerfile build\\\" />\\r\\n      </list>\\r\\n    </recent_temporary>\\r\\n  </component>\\r\\n  <component name=\\\"SharedIndexes\\\">\\r\\n    <attachedChunks>\\r\\n      <set>\\r\\n        <option value=\\\"bundled-jdk-9823dce3aa75-125ca727e0f0-intellij.indexing.shared.core-IU-243.24978.46\\\" />\\r\\n        <option value=\\\"bundled-js-predefined-d6986cc7102b-76f8388c3a79-JavaScript-IU-243.24978.46\\\" />\\r\\n      </set>\\r\\n    </attachedChunks>\\r\\n  </component>\\r\\n  <component name=\\\"SpellCheckerSettings\\\" RuntimeDictionaries=\\\"0\\\" Folders=\\\"0\\\" CustomDictionaries=\\\"0\\\" DefaultDictionary=\\\"application-level\\\" UseSingleDictionary=\\\"true\\\" transferred=\\\"true\\\" />\\r\\n  <component name=\\\"TaskManager\\\">\\r\\n    <task active=\\\"true\\\" id=\\\"Default\\\" summary=\\\"Default task\\\">\\r\\n      <changelist id=\\\"b30e2810-c4c1-4aad-b134-794e52cc1c7d\\\" name=\\\"Changes\\\" comment=\\\"\\\" />\\r\\n      <created>1718816243156</created>\\r\\n      <option name=\\\"number\\\" value=\\\"Default\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"Default\\\" />\\r\\n      <updated>1718816243156</updated>\\r\\n      <workItem from=\\\"1718816244509\\\" duration=\\\"13061000\\\" />\\r\\n      <workItem from=\\\"1718991004992\\\" duration=\\\"25000\\\" />\\r\\n      <workItem from=\\\"1718991057845\\\" duration=\\\"85000\\\" />\\r\\n      <workItem from=\\\"1718991144614\\\" duration=\\\"6968000\\\" />\\r\\n      <workItem from=\\\"1718998317252\\\" duration=\\\"8533000\\\" />\\r\\n      <workItem from=\\\"1719006887776\\\" duration=\\\"7000\\\" />\\r\\n      <workItem from=\\\"1719006951159\\\" duration=\\\"2377000\\\" />\\r\\n      <workItem from=\\\"1719021128819\\\" duration=\\\"3239000\\\" />\\r\\n      <workItem from=\\\"1719083989394\\\" duration=\\\"7971000\\\" />\\r\\n      <workItem from=\\\"1719092003308\\\" duration=\\\"14856000\\\" />\\r\\n      <workItem from=\\\"1719164664347\\\" duration=\\\"2033000\\\" />\\r\\n      <workItem from=\\\"1719166718305\\\" duration=\\\"1783000\\\" />\\r\\n      <workItem from=\\\"1719168519203\\\" duration=\\\"17675000\\\" />\\r\\n      <workItem from=\\\"1719197816332\\\" duration=\\\"1453000\\\" />\\r\\n      <workItem from=\\\"1719273044735\\\" duration=\\\"9847000\\\" />\\r\\n      <workItem from=\\\"1719294110005\\\" duration=\\\"3842000\\\" />\\r\\n      <workItem from=\\\"1719339559458\\\" duration=\\\"303000\\\" />\\r\\n      <workItem from=\\\"1719340295244\\\" duration=\\\"772000\\\" />\\r\\n      <workItem from=\\\"1719363272227\\\" duration=\\\"390000\\\" />\\r\\n      <workItem from=\\\"1719379971872\\\" duration=\\\"8943000\\\" />\\r\\n      <workItem from=\\\"1719464673797\\\" duration=\\\"38000\\\" />\\r\\n      <workItem from=\\\"1719475764139\\\" duration=\\\"14903000\\\" />\\r\\n      <workItem from=\\\"1719492452780\\\" duration=\\\"8000\\\" />\\r\\n      <workItem from=\\\"1719496624579\\\" duration=\\\"6148000\\\" />\\r\\n      <workItem from=\\\"1720542757452\\\" duration=\\\"5355000\\\" />\\r\\n      <workItem from=\\\"1720557527691\\\" duration=\\\"3245000\\\" />\\r\\n      <workItem from=\\\"1720564427492\\\" duration=\\\"1523000\\\" />\\r\\n      <workItem from=\\\"1720613598176\\\" duration=\\\"8000\\\" />\\r\\n      <workItem from=\\\"1720655252208\\\" duration=\\\"3975000\\\" />\\r\\n      <workItem from=\\\"1720661825389\\\" duration=\\\"4305000\\\" />\\r\\n      <workItem from=\\\"1720729165596\\\" duration=\\\"3258000\\\" />\\r\\n      <workItem from=\\\"1720911748039\\\" duration=\\\"331000\\\" />\\r\\n      <workItem from=\\\"1720912096050\\\" duration=\\\"3065000\\\" />\\r\\n      <workItem from=\\\"1740259920741\\\" duration=\\\"7742000\\\" />\\r\\n      <workItem from=\\\"1740270391152\\\" duration=\\\"690000\\\" />\\r\\n      <workItem from=\\\"1740274898695\\\" duration=\\\"2231000\\\" />\\r\\n      <workItem from=\\\"1740295530385\\\" duration=\\\"1120000\\\" />\\r\\n      <workItem from=\\\"1740300354462\\\" duration=\\\"1059000\\\" />\\r\\n      <workItem from=\\\"1740301493702\\\" duration=\\\"8924000\\\" />\\r\\n      <workItem from=\\\"1740318886545\\\" duration=\\\"856000\\\" />\\r\\n      <workItem from=\\\"1740348963270\\\" duration=\\\"388000\\\" />\\r\\n      <workItem from=\\\"1740399426653\\\" duration=\\\"627000\\\" />\\r\\n      <workItem from=\\\"1740459961271\\\" duration=\\\"66000\\\" />\\r\\n      <workItem from=\\\"1740460036909\\\" duration=\\\"8299000\\\" />\\r\\n      <workItem from=\\\"1740490890760\\\" duration=\\\"1889000\\\" />\\r\\n      <workItem from=\\\"1740503199053\\\" duration=\\\"4853000\\\" />\\r\\n      <workItem from=\\\"1740584243965\\\" duration=\\\"17000\\\" />\\r\\n      <workItem from=\\\"1740613094492\\\" duration=\\\"9615000\\\" />\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00080\\\" summary=\\\"fix: ci\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1719588378907</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00080\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00080\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1719588378907</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00081\\\" summary=\\\"fix: ci\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1719588501953</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00081\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00081\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1719588501953</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00082\\\" summary=\\\"fix: ci\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1719588854025</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00082\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00082\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1719588854025</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00083\\\" summary=\\\"fix: ci\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1719589144843</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00083\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00083\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1719589144843</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00084\\\" summary=\\\"chore: jimp types\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1719591262232</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00084\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00084\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1719591262232</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00085\\\" summary=\\\"fix: package.json\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1719591700496</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00085\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00085\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1719591700496</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00086\\\" summary=\\\"feat: playwright report\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1719600693979</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00086\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00086\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1719600693979</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00087\\\" summary=\\\"chore: idea config\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1719600739052</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00087\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00087\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1719600739052</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00088\\\" summary=\\\"feat: sort list\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720545582958</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00088\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00088\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720545582958</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00089\\\" summary=\\\"feat: find most popular ui\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720546921899</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00089\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00089\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720546921899</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00090\\\" summary=\\\"fix: misc\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720558690146</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00090\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00090\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720558690147</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00091\\\" summary=\\\"fix: style\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720564711406</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00091\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00091\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720564711406</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00092\\\" summary=\\\"feat: find unique ui\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720565760853</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00092\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00092\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720565760853</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00093\\\" summary=\\\"feat: group list ui\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720656867853</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00093\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00093\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720656867853</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00094\\\" summary=\\\"feat: reverse list ui\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720658257129</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00094\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00094\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720658257129</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00095\\\" summary=\\\"feat: self host\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720665220407</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00095\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00095\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720665220408</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00096\\\" summary=\\\"chore: format number\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720730102816</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00096\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00096\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720730102817</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00097\\\" summary=\\\"feat: rotate ui\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720913013183</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00097\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00097\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720913013183</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00098\\\" summary=\\\"feat: shuffle ui\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720913810733</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00098\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00098\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720913810733</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00099\\\" summary=\\\"refactor: remove validation schema\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720914492812</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00099\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00099\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720914492812</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00100\\\" summary=\\\"refactor: optimize imports\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720914702655</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00100\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00100\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720914702656</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00101\\\" summary=\\\"chore: use string tools\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1720914810712</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00101\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00101\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1720914810713</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00102\\\" summary=\\\"fix: ctrl v\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740267666455</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00102\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00102\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740267666455</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00103\\\" summary=\\\"feat: update readme\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740276092528</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00103\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00103\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740276092528</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00104\\\" summary=\\\"feat: compress png\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740321721526</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00104\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00104\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740321721526</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00105\\\" summary=\\\"feat: compress png\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740321912140</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00105\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00105\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740321912140</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00106\\\" summary=\\\"fix: compress png\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740322444616</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00106\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00106\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740322444616</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00107\\\" summary=\\\"fix: docs\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740324026721</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00107\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00107\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740324026721</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00108\\\" summary=\\\"fix: docs\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740324069359</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00108\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00108\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740324069359</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00109\\\" summary=\\\"fix: docs\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740324274955</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00109\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00109\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740324274955</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00110\\\" summary=\\\"feat: funding\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740460017596</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00110\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00110\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740460017596</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00111\\\" summary=\\\"feat: ui changes\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740464231905</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00111\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00111\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740464231905</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00112\\\" summary=\\\"feat: ui changes\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740464250449</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00112\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00112\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740464250449</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00113\\\" summary=\\\"fix: tsc\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740464642001</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00113\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00113\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740464642001</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00114\\\" summary=\\\"fix: readme\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740468159111</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00114\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00114\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740468159111</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00115\\\" summary=\\\"fix: broken links\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740488522618</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00115\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00115\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740488522618</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00116\\\" summary=\\\"chore: style buttons\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740490919407</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00116\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00116\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740490919407</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00117\\\" summary=\\\"chore: style\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740491274739</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00117\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00117\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740491274739</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00118\\\" summary=\\\"style: background svg\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740491737480</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00118\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00118\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740491737480</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00119\\\" summary=\\\"docs: img\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740503310227</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00119\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00119\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740503310228</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00120\\\" summary=\\\"fix: bg\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740503419102</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00120\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00120\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740503419102</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00121\\\" summary=\\\"fix: bg\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740504051051</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00121\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00121\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740504051051</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00122\\\" summary=\\\"fix: bg\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740504100676</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00122\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00122\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740504100676</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00123\\\" summary=\\\"fix: bg\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740505390205</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00123\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00123\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740505390205</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00124\\\" summary=\\\"docs: readme\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740614012237</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00124\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00124\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740614012237</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00125\\\" summary=\\\"docs: readme\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740614185980</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00125\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00125\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740614185980</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00126\\\" summary=\\\"chore: handle enter press on search\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740614957672</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00126\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00126\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740614957672</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00127\\\" summary=\\\"chore: show tooloptions in example\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740619610168</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00127\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00127\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740619610169</updated>\\r\\n    </task>\\r\\n    <task id=\\\"LOCAL-00128\\\" summary=\\\"refact: examples\\\">\\r\\n      <option name=\\\"closed\\\" value=\\\"true\\\" />\\r\\n      <created>1740620866551</created>\\r\\n      <option name=\\\"number\\\" value=\\\"00128\\\" />\\r\\n      <option name=\\\"presentableId\\\" value=\\\"LOCAL-00128\\\" />\\r\\n      <option name=\\\"project\\\" value=\\\"LOCAL\\\" />\\r\\n      <updated>1740620866551</updated>\\r\\n    </task>\\r\\n    <option name=\\\"localTasksCounter\\\" value=\\\"129\\\" />\\r\\n    <servers />\\r\\n  </component>\\r\\n  <component name=\\\"TypeScriptGeneratedFilesManager\\\">\\r\\n    <option name=\\\"version\\\" value=\\\"3\\\" />\\r\\n  </component>\\r\\n  <component name=\\\"Vcs.Log.History.Properties\\\">\\r\\n    <option name=\\\"COLUMN_ID_ORDER\\\">\\r\\n      <list>\\r\\n        <option value=\\\"Default.Root\\\" />\\r\\n        <option value=\\\"Default.Author\\\" />\\r\\n        <option value=\\\"Default.Date\\\" />\\r\\n        <option value=\\\"Default.Subject\\\" />\\r\\n        <option value=\\\"Space.CommitStatus\\\" />\\r\\n      </list>\\r\\n    </option>\\r\\n  </component>\\r\\n  <component name=\\\"Vcs.Log.Tabs.Properties\\\">\\r\\n    <option name=\\\"TAB_STATES\\\">\\r\\n      <map>\\r\\n        <entry key=\\\"MAIN\\\">\\r\\n          <value>\\r\\n            <State />\\r\\n          </value>\\r\\n        </entry>\\r\\n      </map>\\r\\n    </option>\\r\\n  </component>\\r\\n  <component name=\\\"VcsManagerConfiguration\\\">\\r\\n    <option name=\\\"CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT\\\" value=\\\"false\\\" />\\r\\n    <option name=\\\"CHECK_NEW_TODO\\\" value=\\\"false\\\" />\\r\\n    <option name=\\\"ADD_EXTERNAL_FILES_SILENTLY\\\" value=\\\"true\\\" />\\r\\n    <MESSAGE value=\\\"chore: format number\\\" />\\r\\n    <MESSAGE value=\\\"feat: rotate ui\\\" />\\r\\n    <MESSAGE value=\\\"feat: shuffle ui\\\" />\\r\\n    <MESSAGE value=\\\"refactor: remove validation schema\\\" />\\r\\n    <MESSAGE value=\\\"refactor: optimize imports\\\" />\\r\\n    <MESSAGE value=\\\"chore: use string tools\\\" />\\r\\n    <MESSAGE value=\\\"fix: ctrl v\\\" />\\r\\n    <MESSAGE value=\\\"feat: update readme\\\" />\\r\\n    <MESSAGE value=\\\"feat: compress png\\\" />\\r\\n    <MESSAGE value=\\\"fix: compress png\\\" />\\r\\n    <MESSAGE value=\\\"fix: docs\\\" />\\r\\n    <MESSAGE value=\\\"feat: funding\\\" />\\r\\n    <MESSAGE value=\\\"feat: ui changes\\\" />\\r\\n    <MESSAGE value=\\\"fix: tsc\\\" />\\r\\n    <MESSAGE value=\\\"fix: readme\\\" />\\r\\n    <MESSAGE value=\\\"fix: broken links\\\" />\\r\\n    <MESSAGE value=\\\"chore: style buttons\\\" />\\r\\n    <MESSAGE value=\\\"chore: style\\\" />\\r\\n    <MESSAGE value=\\\"style: background svg\\\" />\\r\\n    <MESSAGE value=\\\"docs: img\\\" />\\r\\n    <MESSAGE value=\\\"fix: bg\\\" />\\r\\n    <MESSAGE value=\\\"docs: readme\\\" />\\r\\n    <MESSAGE value=\\\"chore: handle enter press on search\\\" />\\r\\n    <MESSAGE value=\\\"chore: show tooloptions in example\\\" />\\r\\n    <MESSAGE value=\\\"refact: examples\\\" />\\r\\n    <option name=\\\"LAST_COMMIT_MESSAGE\\\" value=\\\"refact: examples\\\" />\\r\\n  </component>\\r\\n  <component name=\\\"XSLT-Support.FileAssociations.UIState\\\">\\r\\n    <expand />\\r\\n    <select />\\r\\n  </component>\\r\\n</project>\nSubsystem: com.intellij.openapi.diff.impl.patch.CharsetEP\n<+>UTF-8\n===================================================================\ndiff --git a/.idea/workspace.xml b/.idea/workspace.xml\n--- a/.idea/workspace.xml\t(revision a713690882b7cecea240163d06ee0b3715158faf)\n+++ b/.idea/workspace.xml\t(date 1740653982366)\n@@ -4,14 +4,7 @@\n     <option name=\"autoReloadType\" value=\"SELECTIVE\" />\n   </component>\n   <component name=\"ChangeListManager\">\n-    <list default=\"true\" id=\"b30e2810-c4c1-4aad-b134-794e52cc1c7d\" name=\"Changes\" comment=\"refact: examples\">\n-      <change beforePath=\"$PROJECT_DIR$/.idea/workspace.xml\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/.idea/workspace.xml\" afterDir=\"false\" />\n-      <change beforePath=\"$PROJECT_DIR$/src/components/ToolHeader.tsx\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/components/ToolHeader.tsx\" afterDir=\"false\" />\n-      <change beforePath=\"$PROJECT_DIR$/src/components/examples/Examples.tsx\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/components/examples/ToolExamples.tsx\" afterDir=\"false\" />\n-      <change beforePath=\"$PROJECT_DIR$/src/pages/tools/string/join/index.tsx\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/pages/tools/string/join/index.tsx\" afterDir=\"false\" />\n-      <change beforePath=\"$PROJECT_DIR$/src/pages/tools/string/split/index.tsx\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/pages/tools/string/split/index.tsx\" afterDir=\"false\" />\n-      <change beforePath=\"$PROJECT_DIR$/src/tools/defineTool.tsx\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/tools/defineTool.tsx\" afterDir=\"false\" />\n-    </list>\n+    <list default=\"true\" id=\"b30e2810-c4c1-4aad-b134-794e52cc1c7d\" name=\"Changes\" comment=\"fix: examples\" />\n     <option name=\"SHOW_DIALOG\" value=\"false\" />\n     <option name=\"HIGHLIGHT_CONFLICTS\" value=\"true\" />\n     <option name=\"HIGHLIGHT_NON_ACTIVE_CHANGELIST\" value=\"false\" />\n@@ -62,47 +55,47 @@\n     <option name=\"hideEmptyMiddlePackages\" value=\"true\" />\n     <option name=\"showLibraryContents\" value=\"true\" />\n   </component>\n-  <component name=\"PropertiesComponent\"><![CDATA[{\n-  \"keyToString\": {\n-    \"ASKED_ADD_EXTERNAL_FILES\": \"true\",\n-    \"ASKED_SHARE_PROJECT_CONFIGURATION_FILES\": \"true\",\n-    \"Docker.Dockerfile build.executor\": \"Run\",\n-    \"Docker.Dockerfile.executor\": \"Run\",\n-    \"Playwright.JoinText Component.executor\": \"Run\",\n-    \"Playwright.JoinText Component.should merge text pieces with specified join character.executor\": \"Run\",\n-    \"RunOnceActivity.OpenProjectViewOnStart\": \"true\",\n-    \"RunOnceActivity.ShowReadmeOnStart\": \"true\",\n-    \"RunOnceActivity.git.unshallow\": \"true\",\n-    \"Vitest.compute function (1).executor\": \"Run\",\n-    \"Vitest.compute function.executor\": \"Run\",\n-    \"Vitest.mergeText.executor\": \"Run\",\n-    \"Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor\": \"Run\",\n-    \"Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor\": \"Run\",\n-    \"git-widget-placeholder\": \"examples\",\n-    \"ignore.virus.scanning.warn.message\": \"true\",\n-    \"kotlin-language-version-configured\": \"true\",\n-    \"last_opened_file_path\": \"C:/Users/Ibrahima/IdeaProjects/omni-tools/src/assets\",\n-    \"node.js.detected.package.eslint\": \"true\",\n-    \"node.js.detected.package.tslint\": \"true\",\n-    \"node.js.selected.package.eslint\": \"(autodetect)\",\n-    \"node.js.selected.package.tslint\": \"(autodetect)\",\n-    \"nodejs_package_manager_path\": \"npm\",\n-    \"npm.dev.executor\": \"Run\",\n-    \"npm.lint.executor\": \"Run\",\n-    \"npm.prebuild.executor\": \"Run\",\n-    \"npm.script:create:tool.executor\": \"Run\",\n-    \"npm.test.executor\": \"Run\",\n-    \"npm.test:e2e.executor\": \"Run\",\n-    \"npm.test:e2e:run.executor\": \"Run\",\n-    \"prettierjs.PrettierConfiguration.Package\": \"C:\\\\Users\\\\Ibrahima\\\\IdeaProjects\\\\omni-tools\\\\node_modules\\\\prettier\",\n-    \"project.structure.last.edited\": \"Problems\",\n-    \"project.structure.proportion\": \"0.0\",\n-    \"project.structure.side.proportion\": \"0.2\",\n-    \"settings.editor.selected.configurable\": \"settings.typescriptcompiler\",\n-    \"ts.external.directory.path\": \"C:\\\\Users\\\\Ibrahima\\\\IdeaProjects\\\\omni-tools\\\\node_modules\\\\typescript\\\\lib\",\n-    \"vue.rearranger.settings.migration\": \"true\"\n+  <component name=\"PropertiesComponent\">{\n+  &quot;keyToString&quot;: {\n+    &quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;,\n+    &quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,\n+    &quot;Docker.Dockerfile build.executor&quot;: &quot;Run&quot;,\n+    &quot;Docker.Dockerfile.executor&quot;: &quot;Run&quot;,\n+    &quot;Playwright.JoinText Component.executor&quot;: &quot;Run&quot;,\n+    &quot;Playwright.JoinText Component.should merge text pieces with specified join character.executor&quot;: &quot;Run&quot;,\n+    &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,\n+    &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,\n+    &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,\n+    &quot;Vitest.compute function (1).executor&quot;: &quot;Run&quot;,\n+    &quot;Vitest.compute function.executor&quot;: &quot;Run&quot;,\n+    &quot;Vitest.mergeText.executor&quot;: &quot;Run&quot;,\n+    &quot;Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor&quot;: &quot;Run&quot;,\n+    &quot;Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor&quot;: &quot;Run&quot;,\n+    &quot;git-widget-placeholder&quot;: &quot;examples&quot;,\n+    &quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,\n+    &quot;kotlin-language-version-configured&quot;: &quot;true&quot;,\n+    &quot;last_opened_file_path&quot;: &quot;C:/Users/Ibrahima/IdeaProjects/omni-tools/src/assets&quot;,\n+    &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,\n+    &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,\n+    &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,\n+    &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,\n+    &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,\n+    &quot;npm.dev.executor&quot;: &quot;Run&quot;,\n+    &quot;npm.lint.executor&quot;: &quot;Run&quot;,\n+    &quot;npm.prebuild.executor&quot;: &quot;Run&quot;,\n+    &quot;npm.script:create:tool.executor&quot;: &quot;Run&quot;,\n+    &quot;npm.test.executor&quot;: &quot;Run&quot;,\n+    &quot;npm.test:e2e.executor&quot;: &quot;Run&quot;,\n+    &quot;npm.test:e2e:run.executor&quot;: &quot;Run&quot;,\n+    &quot;prettierjs.PrettierConfiguration.Package&quot;: &quot;C:\\\\Users\\\\Ibrahima\\\\IdeaProjects\\\\omni-tools\\\\node_modules\\\\prettier&quot;,\n+    &quot;project.structure.last.edited&quot;: &quot;Problems&quot;,\n+    &quot;project.structure.proportion&quot;: &quot;0.0&quot;,\n+    &quot;project.structure.side.proportion&quot;: &quot;0.2&quot;,\n+    &quot;settings.editor.selected.configurable&quot;: &quot;settings.typescriptcompiler&quot;,\n+    &quot;ts.external.directory.path&quot;: &quot;C:\\\\Users\\\\Ibrahima\\\\IdeaProjects\\\\omni-tools\\\\node_modules\\\\typescript\\\\lib&quot;,\n+    &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;\n   }\n-}]]></component>\n+}</component>\n   <component name=\"ReactDesignerToolWindowState\">\n     <option name=\"myId2Visible\">\n       <map>\n@@ -265,15 +258,7 @@\n       <workItem from=\"1740490890760\" duration=\"1889000\" />\n       <workItem from=\"1740503199053\" duration=\"4853000\" />\n       <workItem from=\"1740584243965\" duration=\"17000\" />\n-      <workItem from=\"1740613094492\" duration=\"9615000\" />\n-    </task>\n-    <task id=\"LOCAL-00080\" summary=\"fix: ci\">\n-      <option name=\"closed\" value=\"true\" />\n-      <created>1719588378907</created>\n-      <option name=\"number\" value=\"00080\" />\n-      <option name=\"presentableId\" value=\"LOCAL-00080\" />\n-      <option name=\"project\" value=\"LOCAL\" />\n-      <updated>1719588378907</updated>\n+      <workItem from=\"1740613094492\" duration=\"10412000\" />\n     </task>\n     <task id=\"LOCAL-00081\" summary=\"fix: ci\">\n       <option name=\"closed\" value=\"true\" />\n@@ -659,7 +644,15 @@\n       <option name=\"project\" value=\"LOCAL\" />\n       <updated>1740620866551</updated>\n     </task>\n-    <option name=\"localTasksCounter\" value=\"129\" />\n+    <task id=\"LOCAL-00129\" summary=\"fix: examples\">\n+      <option name=\"closed\" value=\"true\" />\n+      <created>1740622869635</created>\n+      <option name=\"number\" value=\"00129\" />\n+      <option name=\"presentableId\" value=\"LOCAL-00129\" />\n+      <option name=\"project\" value=\"LOCAL\" />\n+      <updated>1740622869635</updated>\n+    </task>\n+    <option name=\"localTasksCounter\" value=\"130\" />\n     <servers />\n   </component>\n   <component name=\"TypeScriptGeneratedFilesManager\">\n@@ -691,7 +684,6 @@\n     <option name=\"CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT\" value=\"false\" />\n     <option name=\"CHECK_NEW_TODO\" value=\"false\" />\n     <option name=\"ADD_EXTERNAL_FILES_SILENTLY\" value=\"true\" />\n-    <MESSAGE value=\"chore: format number\" />\n     <MESSAGE value=\"feat: rotate ui\" />\n     <MESSAGE value=\"feat: shuffle ui\" />\n     <MESSAGE value=\"refactor: remove validation schema\" />\n@@ -716,7 +708,8 @@\n     <MESSAGE value=\"chore: handle enter press on search\" />\n     <MESSAGE value=\"chore: show tooloptions in example\" />\n     <MESSAGE value=\"refact: examples\" />\n-    <option name=\"LAST_COMMIT_MESSAGE\" value=\"refact: examples\" />\n+    <MESSAGE value=\"fix: examples\" />\n+    <option name=\"LAST_COMMIT_MESSAGE\" value=\"fix: examples\" />\n   </component>\n   <component name=\"XSLT-Support.FileAssociations.UIState\">\n     <expand />\n"
  },
  {
    "path": ".idea/shelf/Uncommitted_changes_before_Checkout_at_2_27_2025_11_44_AM_[Changes]1/shelved.patch",
    "content": ""
  },
  {
    "path": ".idea/shelf/Uncommitted_changes_before_Checkout_at_2_27_2025_11_44_AM__Changes_.xml",
    "content": "<changelist name=\"Uncommitted_changes_before_Checkout_at_2_27_2025_11_44_AM_[Changes]\" date=\"1740656670640\" recycled=\"false\" toDelete=\"true\">\n  <option name=\"PATH\" value=\"$PROJECT_DIR$/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_27_2025_11_44_AM_[Changes]/shelved.patch\" />\n  <option name=\"DESCRIPTION\" value=\"Uncommitted changes before Checkout at 2/27/2025 11:44 AM [Changes]\" />\n</changelist>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/workspace.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AutoImportSettings\">\n    <option name=\"autoReloadType\" value=\"SELECTIVE\" />\n  </component>\n  <component name=\"ChangeListManager\">\n    <list default=\"true\" id=\"b30e2810-c4c1-4aad-b134-794e52cc1c7d\" name=\"Changes\" comment=\"fix(i18n): Correct &quot;hireMe&quot; translation in navbar&#10;&#10;This commit corrects the translation of &quot;hireMe&quot; in the&#10;navigation bar across all supported languages. The order of&#10;the elements was also fixed to be consistent.\">\n      <change beforePath=\"$PROJECT_DIR$/.idea/workspace.xml\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/.idea/workspace.xml\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/public/locales/es/translation.json\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/public/locales/es/translation.json\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/public/locales/fr/translation.json\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/public/locales/fr/translation.json\" afterDir=\"false\" />\n    </list>\n    <option name=\"SHOW_DIALOG\" value=\"false\" />\n    <option name=\"HIGHLIGHT_CONFLICTS\" value=\"true\" />\n    <option name=\"HIGHLIGHT_NON_ACTIVE_CHANGELIST\" value=\"false\" />\n    <option name=\"LAST_RESOLUTION\" value=\"IGNORE\" />\n  </component>\n  <component name=\"FileTemplateManagerImpl\">\n    <option name=\"RECENT_TEMPLATES\">\n      <list>\n        <option value=\"JavaScript File\" />\n      </list>\n    </option>\n  </component>\n  <component name=\"Git.Merge.Settings\">\n    <option name=\"BRANCH\" value=\"origin/main\" />\n  </component>\n  <component name=\"Git.Settings\">\n    <excluded-from-favorite>\n      <branch-storage>\n        <map>\n          <entry type=\"LOCAL\">\n            <value>\n              <list>\n                <branch-info repo=\"$PROJECT_DIR$\" source=\"main\" />\n              </list>\n            </value>\n          </entry>\n        </map>\n      </branch-storage>\n    </excluded-from-favorite>\n    <option name=\"PUSH_AUTO_UPDATE\" value=\"true\" />\n    <option name=\"RECENT_BRANCH_BY_REPOSITORY\">\n      <map>\n        <entry key=\"$PROJECT_DIR$\" value=\"28f4c64d3044df927dc088435164e803e14f8794\" />\n      </map>\n    </option>\n    <option name=\"RECENT_GIT_ROOT_PATH\" value=\"$PROJECT_DIR$\" />\n    <option name=\"RESET_MODE\" value=\"HARD\" />\n  </component>\n  <component name=\"GitHubPullRequestSearchHistory\">{\n  &quot;history&quot;: [\n    {\n      &quot;assignee&quot;: &quot;iib0011&quot;\n    },\n    {\n      &quot;searchQuery&quot;: &quot;filter&quot;,\n      &quot;state&quot;: &quot;OPEN&quot;\n    },\n    {\n      &quot;state&quot;: &quot;OPEN&quot;\n    }\n  ],\n  &quot;lastFilter&quot;: {\n    &quot;state&quot;: &quot;OPEN&quot;\n  }\n}</component>\n  <component name=\"GitHubPullRequestState\">{\n  &quot;prStates&quot;: [\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts51PkS9&quot;,\n        &quot;number&quot;: 22\n      },\n      &quot;lastSeen&quot;: 1741207144695\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6NiNYl&quot;,\n        &quot;number&quot;: 32\n      },\n      &quot;lastSeen&quot;: 1741209723869\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6Nheyd&quot;,\n        &quot;number&quot;: 31\n      },\n      &quot;lastSeen&quot;: 1741213371410\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6NmRBs&quot;,\n        &quot;number&quot;: 33\n      },\n      &quot;lastSeen&quot;: 1741282429036\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts5zyFTs&quot;,\n        &quot;number&quot;: 15\n      },\n      &quot;lastSeen&quot;: 1741535540953\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6QQB3c&quot;,\n        &quot;number&quot;: 59\n      },\n      &quot;lastSeen&quot;: 1743018960900\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6QMPEg&quot;,\n        &quot;number&quot;: 58\n      },\n      &quot;lastSeen&quot;: 1743019452983\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6QZvRI&quot;,\n        &quot;number&quot;: 61\n      },\n      &quot;lastSeen&quot;: 1743103196866\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6QqPrQ&quot;,\n        &quot;number&quot;: 73\n      },\n      &quot;lastSeen&quot;: 1743265865001\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6Qp5nI&quot;,\n        &quot;number&quot;: 72\n      },\n      &quot;lastSeen&quot;: 1743338472110\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6QsjlS&quot;,\n        &quot;number&quot;: 76\n      },\n      &quot;lastSeen&quot;: 1743352150953\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6Q0JBe&quot;,\n        &quot;number&quot;: 82\n      },\n      &quot;lastSeen&quot;: 1743470267269\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6UE9-x&quot;,\n        &quot;number&quot;: 102\n      },\n      &quot;lastSeen&quot;: 1747171977348\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6XPua_&quot;,\n        &quot;number&quot;: 117\n      },\n      &quot;lastSeen&quot;: 1747929835864\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6XY-mZ&quot;,\n        &quot;number&quot;: 119\n      },\n      &quot;lastSeen&quot;: 1748028108508\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6Xdz4n&quot;,\n        &quot;number&quot;: 120\n      },\n      &quot;lastSeen&quot;: 1748282672214\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6X_zxl&quot;,\n        &quot;number&quot;: 131\n      },\n      &quot;lastSeen&quot;: 1748881279494\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6bhieT&quot;,\n        &quot;number&quot;: 152\n      },\n      &quot;lastSeen&quot;: 1751848489082\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6dOyRk&quot;,\n        &quot;number&quot;: 154\n      },\n      &quot;lastSeen&quot;: 1751849436454\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6cHjNi&quot;,\n        &quot;number&quot;: 153\n      },\n      &quot;lastSeen&quot;: 1751849501498\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6Zs1FN&quot;,\n        &quot;number&quot;: 145\n      },\n      &quot;lastSeen&quot;: 1751849770308\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6bgKi9&quot;,\n        &quot;number&quot;: 150\n      },\n      &quot;lastSeen&quot;: 1751850367300\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6eUKC-&quot;,\n        &quot;number&quot;: 176\n      },\n      &quot;lastSeen&quot;: 1752158748013\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6et6vx&quot;,\n        &quot;number&quot;: 192\n      },\n      &quot;lastSeen&quot;: 1752585709582\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6d36mi&quot;,\n        &quot;number&quot;: 168\n      },\n      &quot;lastSeen&quot;: 1752805763664\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6fnXKf&quot;,\n        &quot;number&quot;: 208\n      },\n      &quot;lastSeen&quot;: 1752862212326\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6rjINx&quot;,\n        &quot;number&quot;: 259\n      },\n      &quot;lastSeen&quot;: 1759434090574\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6qcP13&quot;,\n        &quot;number&quot;: 256\n      },\n      &quot;lastSeen&quot;: 1759434257615\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6ow8QZ&quot;,\n        &quot;number&quot;: 252\n      },\n      &quot;lastSeen&quot;: 1759434340504\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6myVeZ&quot;,\n        &quot;number&quot;: 247\n      },\n      &quot;lastSeen&quot;: 1759434588110\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6i5ZAq&quot;,\n        &quot;number&quot;: 239\n      },\n      &quot;lastSeen&quot;: 1759434599664\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6iiuGd&quot;,\n        &quot;number&quot;: 237\n      },\n      &quot;lastSeen&quot;: 1759434652702\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6gwm8n&quot;,\n        &quot;number&quot;: 230\n      },\n      &quot;lastSeen&quot;: 1759434669914\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6f5JeZ&quot;,\n        &quot;number&quot;: 220\n      },\n      &quot;lastSeen&quot;: 1759434706785\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6ftgWI&quot;,\n        &quot;number&quot;: 217\n      },\n      &quot;lastSeen&quot;: 1759434804548\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6XsHfL&quot;,\n        &quot;number&quot;: 128\n      },\n      &quot;lastSeen&quot;: 1759434870000\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6ec-tz&quot;,\n        &quot;number&quot;: 180\n      },\n      &quot;lastSeen&quot;: 1759434882113\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6fsi5n&quot;,\n        &quot;number&quot;: 216\n      },\n      &quot;lastSeen&quot;: 1759434902813\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6ZkP3F&quot;,\n        &quot;number&quot;: 142\n      },\n      &quot;lastSeen&quot;: 1759434918778\n    },\n    {\n      &quot;id&quot;: {\n        &quot;id&quot;: &quot;PR_kwDOMJIfts6qcbuA&quot;,\n        &quot;number&quot;: 257\n      },\n      &quot;lastSeen&quot;: 1759438234107\n    }\n  ]\n}</component>\n  <component name=\"GithubPullRequestsUISettings\">{\n  &quot;selectedUrlAndAccountId&quot;: {\n    &quot;url&quot;: &quot;https://github.com/iib0011/omni-tools.git&quot;,\n    &quot;accountId&quot;: &quot;45f8cd51-000f-4ba4-a4c6-c4d96ac9b1e5&quot;\n  }\n}</component>\n  <component name=\"GoLibraries\">\n    <option name=\"indexEntireGoPath\" value=\"true\" />\n  </component>\n  <component name=\"HighlightingSettingsPerFile\">\n    <setting file=\"file://$PROJECT_DIR$/node_modules/react-image-crop/dist/index.d.ts\" root0=\"SKIP_INSPECTION\" />\n  </component>\n  <component name=\"KubernetesApiProvider\">{\n  &quot;isMigrated&quot;: true\n}</component>\n  <component name=\"MarkdownSettingsMigration\">\n    <option name=\"stateVersion\" value=\"1\" />\n  </component>\n  <component name=\"ProjectColorInfo\">{\n    &quot;associatedIndex&quot;: 0\n    }</component>\n  <component name=\"ProjectId\" id=\"2i6g3WkUxdCURKYvUIJ9LMY5Qsc\" />\n  <component name=\"ProjectLevelVcsManager\" settingsEditedManually=\"true\">\n    <OptionsSetting value=\"false\" id=\"Update\" />\n    <ConfirmationsSetting value=\"2\" id=\"Add\" />\n  </component>\n  <component name=\"ProjectViewState\">\n    <option name=\"autoscrollFromSource\" value=\"true\" />\n    <option name=\"hideEmptyMiddlePackages\" value=\"true\" />\n    <option name=\"showLibraryContents\" value=\"true\" />\n  </component>\n  <component name=\"PropertiesComponent\">{\n  &quot;keyToString&quot;: {\n    &quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;,\n    &quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,\n    &quot;Docker.Dockerfile build.executor&quot;: &quot;Run&quot;,\n    &quot;Docker.Dockerfile.executor&quot;: &quot;Run&quot;,\n    &quot;Node.js.add-i18n-to-meta.js.executor&quot;: &quot;Run&quot;,\n    &quot;Node.js.locize-upload.js.executor&quot;: &quot;Run&quot;,\n    &quot;Node.js.update-i18n-from-meta.js.executor&quot;: &quot;Run&quot;,\n    &quot;Playwright.Create transparent PNG.should make png color transparent.executor&quot;: &quot;Run&quot;,\n    &quot;Playwright.JoinText Component.executor&quot;: &quot;Run&quot;,\n    &quot;Playwright.JoinText Component.should merge text pieces with specified join character.executor&quot;: &quot;Run&quot;,\n    &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,\n    &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,\n    &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,\n    &quot;Vitest.compute function (1).executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.compute function.executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.generatePassword.executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.mergeText.executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.parsePageRanges.executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.removeDuplicateLines function.executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.removeDuplicateLines function.newlines option.executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.replaceText function.executor&quot;: &quot;Run&quot;,\n    &quot;Vitest.timeBetweenDates.executor&quot;: &quot;Run&quot;,\n    &quot;git-widget-placeholder&quot;: &quot;main&quot;,\n    &quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,\n    &quot;kotlin-language-version-configured&quot;: &quot;true&quot;,\n    &quot;last_opened_file_path&quot;: &quot;C:/Users/Ibrahima/IdeaProjects/omni-tools&quot;,\n    &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,\n    &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,\n    &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,\n    &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,\n    &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,\n    &quot;npm.build.executor&quot;: &quot;Run&quot;,\n    &quot;npm.dev.executor&quot;: &quot;Run&quot;,\n    &quot;npm.i18n:extract.executor&quot;: &quot;Run&quot;,\n    &quot;npm.i18n:pull.executor&quot;: &quot;Run&quot;,\n    &quot;npm.i18n:push.executor&quot;: &quot;Run&quot;,\n    &quot;npm.i18n:sync.executor&quot;: &quot;Run&quot;,\n    &quot;npm.lint.executor&quot;: &quot;Run&quot;,\n    &quot;npm.prebuild.executor&quot;: &quot;Run&quot;,\n    &quot;npm.script:create:tool.executor&quot;: &quot;Run&quot;,\n    &quot;npm.test.executor&quot;: &quot;Run&quot;,\n    &quot;npm.test:e2e.executor&quot;: &quot;Run&quot;,\n    &quot;npm.test:e2e:run.executor&quot;: &quot;Run&quot;,\n    &quot;npm.typecheck.executor&quot;: &quot;Run&quot;,\n    &quot;prettierjs.PrettierConfiguration.Package&quot;: &quot;C:\\\\Users\\\\Ibrahima\\\\IdeaProjects\\\\omni-tools\\\\node_modules\\\\prettier&quot;,\n    &quot;project.structure.last.edited&quot;: &quot;Problems&quot;,\n    &quot;project.structure.proportion&quot;: &quot;0.0&quot;,\n    &quot;project.structure.side.proportion&quot;: &quot;0.2&quot;,\n    &quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,\n    &quot;ts.external.directory.path&quot;: &quot;C:\\\\Users\\\\Ibrahima\\\\IdeaProjects\\\\omni-tools\\\\node_modules\\\\typescript\\\\lib&quot;,\n    &quot;ts.rename.search.for.js.occurrences&quot;: &quot;false&quot;,\n    &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;\n  }\n}</component>\n  <component name=\"ReactDesignerToolWindowState\">\n    <option name=\"myId2Visible\">\n      <map>\n        <entry key=\"com.intellij.reactbuddy.reactComponents\" value=\"false\" />\n        <entry key=\"com.intellij.reactbuddy.reactInspector\" value=\"false\" />\n        <entry key=\"com.intellij.reactbuddy.storybook\" value=\"false\" />\n      </map>\n    </option>\n  </component>\n  <component name=\"RecentsManager\">\n    <key name=\"CopyFile.RECENT_KEYS\">\n      <recent name=\"C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\" />\n      <recent name=\"C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\src\" />\n      <recent name=\"C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\public\" />\n      <recent name=\"C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\src\\assets\" />\n      <recent name=\"C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\public\\assets\\fonts\\quicksand\" />\n    </key>\n    <key name=\"MoveFile.RECENT_KEYS\">\n      <recent name=\"C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\@types\" />\n      <recent name=\"C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\src\\lib\\ghostscript\" />\n      <recent name=\"C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\public\\assets\" />\n      <recent name=\"C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\src\\pages\\tools\" />\n      <recent name=\"C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\src\\pages\\categories\" />\n    </key>\n  </component>\n  <component name=\"RunManager\" selected=\"npm.dev\">\n    <configuration default=\"true\" type=\"docker-deploy\" factoryName=\"dockerfile\" temporary=\"true\">\n      <deployment type=\"dockerfile\">\n        <settings />\n      </deployment>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"dev\" type=\"js.build_tools.npm\" temporary=\"true\" nameIsGenerated=\"true\">\n      <package-json value=\"$PROJECT_DIR$/package.json\" />\n      <command value=\"run\" />\n      <scripts>\n        <script value=\"dev\" />\n      </scripts>\n      <node-interpreter value=\"project\" />\n      <envs>\n        <env name=\"LOCIZE_API_KEY\" value=\"a2ac4dc2-d10e-4d35-bcf7-92db87381711\" />\n        <env name=\"VITE_CONTRIBUTOR_MODE\" value=\"true\" />\n      </envs>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"i18n:sync\" type=\"js.build_tools.npm\" temporary=\"true\" nameIsGenerated=\"true\">\n      <package-json value=\"$PROJECT_DIR$/package.json\" />\n      <command value=\"run\" />\n      <scripts>\n        <script value=\"i18n:sync\" />\n      </scripts>\n      <node-interpreter value=\"project\" />\n      <envs>\n        <env name=\"LOCIZE_API_KEY\" value=\"a2ac4dc2-d10e-4d35-bcf7-92db87381711\" />\n      </envs>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"test\" type=\"js.build_tools.npm\" temporary=\"true\" nameIsGenerated=\"true\">\n      <package-json value=\"$PROJECT_DIR$/package.json\" />\n      <command value=\"run\" />\n      <scripts>\n        <script value=\"test\" />\n      </scripts>\n      <node-interpreter value=\"project\" />\n      <envs />\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"test:e2e\" type=\"js.build_tools.npm\" temporary=\"true\" nameIsGenerated=\"true\">\n      <package-json value=\"$PROJECT_DIR$/package.json\" />\n      <command value=\"run\" />\n      <scripts>\n        <script value=\"test:e2e\" />\n      </scripts>\n      <node-interpreter value=\"project\" />\n      <envs />\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"typecheck\" type=\"js.build_tools.npm\" temporary=\"true\" nameIsGenerated=\"true\">\n      <package-json value=\"$PROJECT_DIR$/package.json\" />\n      <command value=\"run\" />\n      <scripts>\n        <script value=\"typecheck\" />\n      </scripts>\n      <node-interpreter value=\"project\" />\n      <envs />\n      <method v=\"2\" />\n    </configuration>\n    <list>\n      <item itemvalue=\"npm.test\" />\n      <item itemvalue=\"npm.test:e2e\" />\n      <item itemvalue=\"npm.typecheck\" />\n      <item itemvalue=\"npm.i18n:sync\" />\n      <item itemvalue=\"npm.dev\" />\n    </list>\n    <recent_temporary>\n      <list>\n        <item itemvalue=\"npm.dev\" />\n        <item itemvalue=\"npm.i18n:sync\" />\n        <item itemvalue=\"npm.test:e2e\" />\n        <item itemvalue=\"npm.test\" />\n        <item itemvalue=\"npm.typecheck\" />\n      </list>\n    </recent_temporary>\n  </component>\n  <component name=\"SharedIndexes\">\n    <attachedChunks>\n      <set>\n        <option value=\"bundled-jdk-9823dce3aa75-125ca727e0f0-intellij.indexing.shared.core-IU-243.24978.46\" />\n        <option value=\"bundled-js-predefined-d6986cc7102b-76f8388c3a79-JavaScript-IU-243.24978.46\" />\n      </set>\n    </attachedChunks>\n  </component>\n  <component name=\"SpellCheckerSettings\" RuntimeDictionaries=\"0\" Folders=\"0\" CustomDictionaries=\"0\" DefaultDictionary=\"application-level\" UseSingleDictionary=\"true\" transferred=\"true\" />\n  <component name=\"TaskManager\">\n    <task active=\"true\" id=\"Default\" summary=\"Default task\">\n      <changelist id=\"b30e2810-c4c1-4aad-b134-794e52cc1c7d\" name=\"Changes\" comment=\"\" />\n      <created>1718816243156</created>\n      <option name=\"number\" value=\"Default\" />\n      <option name=\"presentableId\" value=\"Default\" />\n      <updated>1718816243156</updated>\n      <workItem from=\"1718816244509\" duration=\"13061000\" />\n      <workItem from=\"1718991004992\" duration=\"25000\" />\n      <workItem from=\"1718991057845\" duration=\"85000\" />\n      <workItem from=\"1718991144614\" duration=\"6968000\" />\n      <workItem from=\"1718998317252\" duration=\"8533000\" />\n      <workItem from=\"1719006887776\" duration=\"7000\" />\n      <workItem from=\"1719006951159\" duration=\"2377000\" />\n      <workItem from=\"1719021128819\" duration=\"3239000\" />\n      <workItem from=\"1719083989394\" duration=\"7971000\" />\n      <workItem from=\"1719092003308\" duration=\"14856000\" />\n      <workItem from=\"1719164664347\" duration=\"2033000\" />\n      <workItem from=\"1719166718305\" duration=\"1783000\" />\n      <workItem from=\"1719168519203\" duration=\"17675000\" />\n      <workItem from=\"1719197816332\" duration=\"1453000\" />\n      <workItem from=\"1719273044735\" duration=\"9847000\" />\n      <workItem from=\"1719294110005\" duration=\"3842000\" />\n      <workItem from=\"1719339559458\" duration=\"303000\" />\n      <workItem from=\"1719340295244\" duration=\"772000\" />\n      <workItem from=\"1719363272227\" duration=\"390000\" />\n      <workItem from=\"1719379971872\" duration=\"8943000\" />\n      <workItem from=\"1719464673797\" duration=\"38000\" />\n      <workItem from=\"1719475764139\" duration=\"14903000\" />\n      <workItem from=\"1719492452780\" duration=\"8000\" />\n      <workItem from=\"1719496624579\" duration=\"6148000\" />\n      <workItem from=\"1720542757452\" duration=\"5355000\" />\n      <workItem from=\"1720557527691\" duration=\"3245000\" />\n      <workItem from=\"1720564427492\" duration=\"1523000\" />\n      <workItem from=\"1720613598176\" duration=\"8000\" />\n      <workItem from=\"1720655252208\" duration=\"3975000\" />\n      <workItem from=\"1720661825389\" duration=\"4305000\" />\n      <workItem from=\"1720729165596\" duration=\"3258000\" />\n      <workItem from=\"1720911748039\" duration=\"331000\" />\n      <workItem from=\"1720912096050\" duration=\"3065000\" />\n      <workItem from=\"1740259920741\" duration=\"7742000\" />\n      <workItem from=\"1740270391152\" duration=\"690000\" />\n      <workItem from=\"1740274898695\" duration=\"2231000\" />\n      <workItem from=\"1740295530385\" duration=\"1120000\" />\n      <workItem from=\"1740300354462\" duration=\"1059000\" />\n      <workItem from=\"1740301493702\" duration=\"8924000\" />\n      <workItem from=\"1740318886545\" duration=\"856000\" />\n      <workItem from=\"1740348963270\" duration=\"388000\" />\n      <workItem from=\"1740399426653\" duration=\"627000\" />\n      <workItem from=\"1740459961271\" duration=\"66000\" />\n      <workItem from=\"1740460036909\" duration=\"8299000\" />\n      <workItem from=\"1740490890760\" duration=\"1889000\" />\n      <workItem from=\"1740503199053\" duration=\"4853000\" />\n      <workItem from=\"1740584243965\" duration=\"17000\" />\n      <workItem from=\"1740613094492\" duration=\"9615000\" />\n      <workItem from=\"1740664266923\" duration=\"145000\" />\n      <workItem from=\"1740665190253\" duration=\"496000\" />\n      <workItem from=\"1740670449847\" duration=\"4776000\" />\n      <workItem from=\"1740702343843\" duration=\"657000\" />\n      <workItem from=\"1740788381920\" duration=\"465000\" />\n      <workItem from=\"1740788856134\" duration=\"659000\" />\n      <workItem from=\"1740880919391\" duration=\"4395000\" />\n      <workItem from=\"1740923024259\" duration=\"23000\" />\n      <workItem from=\"1740933006573\" duration=\"3679000\" />\n      <workItem from=\"1741475969294\" duration=\"4215000\" />\n      <workItem from=\"1741494053121\" duration=\"178000\" />\n      <workItem from=\"1741537936314\" duration=\"1294000\" />\n      <workItem from=\"1741539602311\" duration=\"4557000\" />\n      <workItem from=\"1741547560596\" duration=\"1671000\" />\n      <workItem from=\"1741567442768\" duration=\"14127000\" />\n      <workItem from=\"1741971589699\" duration=\"371000\" />\n      <workItem from=\"1743018497879\" duration=\"3895000\" />\n      <workItem from=\"1743047367993\" duration=\"986000\" />\n      <workItem from=\"1743103182313\" duration=\"4264000\" />\n      <workItem from=\"1743348610793\" duration=\"21855000\" />\n      <workItem from=\"1743397561176\" duration=\"25000\" />\n      <workItem from=\"1743458576265\" duration=\"13083000\" />\n      <workItem from=\"1743690613245\" duration=\"77000\" />\n      <workItem from=\"1743691250813\" duration=\"1550000\" />\n      <workItem from=\"1743699386059\" duration=\"11195000\" />\n      <workItem from=\"1743782726563\" duration=\"2444000\" />\n      <workItem from=\"1743811558991\" duration=\"1279000\" />\n      <workItem from=\"1745523972292\" duration=\"3000\" />\n      <workItem from=\"1745687713234\" duration=\"1747000\" />\n      <workItem from=\"1745775228478\" duration=\"1221000\" />\n      <workItem from=\"1745835676024\" duration=\"68000\" />\n      <workItem from=\"1747171958176\" duration=\"1105000\" />\n      <workItem from=\"1747217211469\" duration=\"4000\" />\n      <workItem from=\"1747929815472\" duration=\"843000\" />\n      <workItem from=\"1748026506667\" duration=\"2536000\" />\n      <workItem from=\"1748282636141\" duration=\"478000\" />\n      <workItem from=\"1749047510481\" duration=\"879000\" />\n      <workItem from=\"1751846528195\" duration=\"4358000\" />\n      <workItem from=\"1752070315115\" duration=\"19000\" />\n      <workItem from=\"1752071020011\" duration=\"1599000\" />\n      <workItem from=\"1752077170501\" duration=\"4261000\" />\n      <workItem from=\"1752127185450\" duration=\"1168000\" />\n      <workItem from=\"1752157409587\" duration=\"2415000\" />\n      <workItem from=\"1752403829295\" duration=\"13253000\" />\n      <workItem from=\"1752493585622\" duration=\"11629000\" />\n      <workItem from=\"1752507105323\" duration=\"9008000\" />\n      <workItem from=\"1752875788332\" duration=\"3566000\" />\n      <workItem from=\"1753099267173\" duration=\"21000\" />\n      <workItem from=\"1753123130080\" duration=\"4054000\" />\n      <workItem from=\"1753201599796\" duration=\"4449000\" />\n      <workItem from=\"1753206561770\" duration=\"119000\" />\n      <workItem from=\"1753206717510\" duration=\"3599000\" />\n      <workItem from=\"1759497758761\" duration=\"1012000\" />\n      <workItem from=\"1759502144651\" duration=\"1106000\" />\n    </task>\n    <task id=\"LOCAL-00210\" summary=\"feat: convert to jpg\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752026153328</created>\n      <option name=\"number\" value=\"00210\" />\n      <option name=\"presentableId\" value=\"LOCAL-00210\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752026153328</updated>\n    </task>\n    <task id=\"LOCAL-00211\" summary=\"feat: edit image\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752032092273</created>\n      <option name=\"number\" value=\"00211\" />\n      <option name=\"presentableId\" value=\"LOCAL-00211\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752032092274</updated>\n    </task>\n    <task id=\"LOCAL-00212\" summary=\"fix: favicons\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752071147050</created>\n      <option name=\"number\" value=\"00212\" />\n      <option name=\"presentableId\" value=\"LOCAL-00212\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752071147050</updated>\n    </task>\n    <task id=\"LOCAL-00213\" summary=\"chore: examples button visibility\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752079671580</created>\n      <option name=\"number\" value=\"00213\" />\n      <option name=\"presentableId\" value=\"LOCAL-00213\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752079671580</updated>\n    </task>\n    <task id=\"LOCAL-00214\" summary=\"feat: pdf editor\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752079879005</created>\n      <option name=\"number\" value=\"00214\" />\n      <option name=\"presentableId\" value=\"LOCAL-00214\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752079879005</updated>\n    </task>\n    <task id=\"LOCAL-00215\" summary=\"chore: style link\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752080307348</created>\n      <option name=\"number\" value=\"00215\" />\n      <option name=\"presentableId\" value=\"LOCAL-00215\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752080307349</updated>\n    </task>\n    <task id=\"LOCAL-00216\" summary=\"refactor: PDF editor\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752157851370</created>\n      <option name=\"number\" value=\"00216\" />\n      <option name=\"presentableId\" value=\"LOCAL-00216\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752157851371</updated>\n    </task>\n    <task id=\"LOCAL-00217\" summary=\"docs: edit pdf meta\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752158119802</created>\n      <option name=\"number\" value=\"00217\" />\n      <option name=\"presentableId\" value=\"LOCAL-00217\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752158119802</updated>\n    </task>\n    <task id=\"LOCAL-00218\" summary=\"fix: misc\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752402313190</created>\n      <option name=\"number\" value=\"00218\" />\n      <option name=\"presentableId\" value=\"LOCAL-00218\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752402313191</updated>\n    </task>\n    <task id=\"LOCAL-00219\" summary=\"fix: i18n tsc\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752408068771</created>\n      <option name=\"number\" value=\"00219\" />\n      <option name=\"presentableId\" value=\"LOCAL-00219\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752408068771</updated>\n    </task>\n    <task id=\"LOCAL-00220\" summary=\"fix: i18n tsc\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752412149075</created>\n      <option name=\"number\" value=\"00220\" />\n      <option name=\"presentableId\" value=\"LOCAL-00220\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752412149075</updated>\n    </task>\n    <task id=\"LOCAL-00221\" summary=\"fix: tsc\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752422405814</created>\n      <option name=\"number\" value=\"00221\" />\n      <option name=\"presentableId\" value=\"LOCAL-00221\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752422405814</updated>\n    </task>\n    <task id=\"LOCAL-00222\" summary=\"chore: locize upload\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752423460080</created>\n      <option name=\"number\" value=\"00222\" />\n      <option name=\"presentableId\" value=\"LOCAL-00222\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752423460080</updated>\n    </task>\n    <task id=\"LOCAL-00223\" summary=\"chore: i18n in meta\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752493634215</created>\n      <option name=\"number\" value=\"00223\" />\n      <option name=\"presentableId\" value=\"LOCAL-00223\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752493634215</updated>\n    </task>\n    <task id=\"LOCAL-00224\" summary=\"chore: add i18n to meta script\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752497705915</created>\n      <option name=\"number\" value=\"00224\" />\n      <option name=\"presentableId\" value=\"LOCAL-00224\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752497705915</updated>\n    </task>\n    <task id=\"LOCAL-00225\" summary=\"fix: tsc\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752501110885</created>\n      <option name=\"number\" value=\"00225\" />\n      <option name=\"presentableId\" value=\"LOCAL-00225\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752501110885</updated>\n    </task>\n    <task id=\"LOCAL-00226\" summary=\"chore: bundle translations at build time\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752503206380</created>\n      <option name=\"number\" value=\"00226\" />\n      <option name=\"presentableId\" value=\"LOCAL-00226\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752503206380</updated>\n    </task>\n    <task id=\"LOCAL-00227\" summary=\"fix: tsc\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752503720380</created>\n      <option name=\"number\" value=\"00227\" />\n      <option name=\"presentableId\" value=\"LOCAL-00227\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752503720380</updated>\n    </task>\n    <task id=\"LOCAL-00228\" summary=\"chore: remove unnecessary\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752503770543</created>\n      <option name=\"number\" value=\"00228\" />\n      <option name=\"presentableId\" value=\"LOCAL-00228\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752503770543</updated>\n    </task>\n    <task id=\"LOCAL-00229\" summary=\"chore: saveMissing\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752505593881</created>\n      <option name=\"number\" value=\"00229\" />\n      <option name=\"presentableId\" value=\"LOCAL-00229\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752505593881</updated>\n    </task>\n    <task id=\"LOCAL-00230\" summary=\"fix: translation related behaviors\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752512678963</created>\n      <option name=\"number\" value=\"00230\" />\n      <option name=\"presentableId\" value=\"LOCAL-00230\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752512678963</updated>\n    </task>\n    <task id=\"LOCAL-00231\" summary=\"feat: password generator to test translation\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752514466233</created>\n      <option name=\"number\" value=\"00231\" />\n      <option name=\"presentableId\" value=\"LOCAL-00231\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752514466233</updated>\n    </task>\n    <task id=\"LOCAL-00232\" summary=\"docs: translation docs\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752515675314</created>\n      <option name=\"number\" value=\"00232\" />\n      <option name=\"presentableId\" value=\"LOCAL-00232\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752515675314</updated>\n    </task>\n    <task id=\"LOCAL-00233\" summary=\"fix: translations\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752585351036</created>\n      <option name=\"number\" value=\"00233\" />\n      <option name=\"presentableId\" value=\"LOCAL-00233\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752585351036</updated>\n    </task>\n    <task id=\"LOCAL-00234\" summary=\"chore: delete unused i18n json files\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752585829343</created>\n      <option name=\"number\" value=\"00234\" />\n      <option name=\"presentableId\" value=\"LOCAL-00234\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752585829343</updated>\n    </task>\n    <task id=\"LOCAL-00235\" summary=\"fix: create-tool.mjs to use i18n object\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752586932190</created>\n      <option name=\"number\" value=\"00235\" />\n      <option name=\"presentableId\" value=\"LOCAL-00235\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752586932190</updated>\n    </task>\n    <task id=\"LOCAL-00236\" summary=\"fix: show Use this tool only if medium breakpoint\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752591387066</created>\n      <option name=\"number\" value=\"00236\" />\n      <option name=\"presentableId\" value=\"LOCAL-00236\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752591387066</updated>\n    </task>\n    <task id=\"LOCAL-00237\" summary=\"chore: sync locales\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752596436284</created>\n      <option name=\"number\" value=\"00237\" />\n      <option name=\"presentableId\" value=\"LOCAL-00237\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752596436284</updated>\n    </task>\n    <task id=\"LOCAL-00238\" summary=\"fix: i18n\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752600606853</created>\n      <option name=\"number\" value=\"00238\" />\n      <option name=\"presentableId\" value=\"LOCAL-00238\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752600606853</updated>\n    </task>\n    <task id=\"LOCAL-00239\" summary=\"fix: i18n\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752601115085</created>\n      <option name=\"number\" value=\"00239\" />\n      <option name=\"presentableId\" value=\"LOCAL-00239\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752601115085</updated>\n    </task>\n    <task id=\"LOCAL-00240\" summary=\"chore: remove prebuild\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752601506346</created>\n      <option name=\"number\" value=\"00240\" />\n      <option name=\"presentableId\" value=\"LOCAL-00240\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752601506346</updated>\n    </task>\n    <task id=\"LOCAL-00241\" summary=\"fix: broken translations\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752601987121</created>\n      <option name=\"number\" value=\"00241\" />\n      <option name=\"presentableId\" value=\"LOCAL-00241\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752601987121</updated>\n    </task>\n    <task id=\"LOCAL-00242\" summary=\"fix: i18n tsc\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752604958929</created>\n      <option name=\"number\" value=\"00242\" />\n      <option name=\"presentableId\" value=\"LOCAL-00242\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752604958929</updated>\n    </task>\n    <task id=\"LOCAL-00243\" summary=\"chore: i18n pull dutch\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752605940802</created>\n      <option name=\"number\" value=\"00243\" />\n      <option name=\"presentableId\" value=\"LOCAL-00243\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752605940802</updated>\n    </task>\n    <task id=\"LOCAL-00244\" summary=\"chore: sync locize\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1752805853344</created>\n      <option name=\"number\" value=\"00244\" />\n      <option name=\"presentableId\" value=\"LOCAL-00244\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1752805853344</updated>\n    </task>\n    <task id=\"LOCAL-00245\" summary=\"feat: language browser detection\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1753124389709</created>\n      <option name=\"number\" value=\"00245\" />\n      <option name=\"presentableId\" value=\"LOCAL-00245\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1753124389709</updated>\n    </task>\n    <task id=\"LOCAL-00246\" summary=\"fix: misc\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1753206794968</created>\n      <option name=\"number\" value=\"00246\" />\n      <option name=\"presentableId\" value=\"LOCAL-00246\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1753206794968</updated>\n    </task>\n    <task id=\"LOCAL-00247\" summary=\"chore: show only necessary tags on a category\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1753207817041</created>\n      <option name=\"number\" value=\"00247\" />\n      <option name=\"presentableId\" value=\"LOCAL-00247\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1753207817041</updated>\n    </task>\n    <task id=\"LOCAL-00248\" summary=\"chore: CATEGORIES_USER_TYPES_MAPPINGS\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1753209484099</created>\n      <option name=\"number\" value=\"00248\" />\n      <option name=\"presentableId\" value=\"LOCAL-00248\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1753209484099</updated>\n    </task>\n    <task id=\"LOCAL-00249\" summary=\"chore: translate userTypes\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1753210033390</created>\n      <option name=\"number\" value=\"00249\" />\n      <option name=\"presentableId\" value=\"LOCAL-00249\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1753210033390</updated>\n    </task>\n    <task id=\"LOCAL-00250\" summary=\"feat: remove temperature conversion from generic-calc&#10;&#10;This commit removes the temperature conversion tool from the&#10;generic-calc tool. This is because the tool was causing issues.&#10;&#10;The following files were modified:&#10;- src/pages/tools/number/generic-calc/data/index.ts&#10;- src/pages/tools/number/generic-calc/data/temperature.ts&#10;- package.json&#10;- .idea/workspace.xml\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1759439927012</created>\n      <option name=\"number\" value=\"00250\" />\n      <option name=\"presentableId\" value=\"LOCAL-00250\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1759439927012</updated>\n    </task>\n    <task id=\"LOCAL-00251\" summary=\"feat: Remove onnxruntime-web dependency (main)&#10;&#10;This commit removes the onnxruntime-web package&#10;from package.json.&#10;```\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1759441368519</created>\n      <option name=\"number\" value=\"00251\" />\n      <option name=\"presentableId\" value=\"LOCAL-00251\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1759441368519</updated>\n    </task>\n    <task id=\"LOCAL-00252\" summary=\"fix: onnxruntime-web version\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1759441908841</created>\n      <option name=\"number\" value=\"00252\" />\n      <option name=\"presentableId\" value=\"LOCAL-00252\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1759441908841</updated>\n    </task>\n    <task id=\"LOCAL-00253\" summary=\"feat: Upgrade Node.js versions in CI (main)&#10;&#10;This commit updates the Node.js versions used in the CI&#10;workflows to version 20. This ensures that the CI&#10;environment uses a more up-to-date and supported version&#10;of Node.js.\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1759442128298</created>\n      <option name=\"number\" value=\"00253\" />\n      <option name=\"presentableId\" value=\"LOCAL-00253\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1759442128299</updated>\n    </task>\n    <task id=\"LOCAL-00254\" summary=\"feat: Upgrade onnxruntime-web and onnxruntime-common (main)&#10;&#10;Upgrades onnxruntime-web and onnxruntime-common to versions&#10;1.23.0. This includes updates to dependencies and related&#10;packages.\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1759442775014</created>\n      <option name=\"number\" value=\"00254\" />\n      <option name=\"presentableId\" value=\"LOCAL-00254\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1759442775014</updated>\n    </task>\n    <task id=\"LOCAL-00255\" summary=\"chore: Replace &quot;Buy me a coffee&quot; with &quot;Hire me&quot; (main)&#10;&#10;This commit replaces the &quot;Buy me a coffee&quot; button with a&#10;&quot;Hire me&quot; button in the navbar. It also updates the&#10;corresponding translation in the `en/translation.json` file.&#10;The icon has been changed to a job search icon, and the&#10;link points to a Google Drive document.\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1759502472647</created>\n      <option name=\"number\" value=\"00255\" />\n      <option name=\"presentableId\" value=\"LOCAL-00255\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1759502472647</updated>\n    </task>\n    <task id=\"LOCAL-00256\" summary=\"feat: Update workspace and translation files (main)&#10;&#10;This commit includes the following changes:&#10;&#10;- Updated the workspace configuration in .idea/workspace.xml to&#10;  reflect the recent npm script execution.&#10;- Modified the English translation file (translation.json) by&#10;  reordering the &quot;navbar&quot; keys.\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1759502707923</created>\n      <option name=\"number\" value=\"00256\" />\n      <option name=\"presentableId\" value=\"LOCAL-00256\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1759502707923</updated>\n    </task>\n    <task id=\"LOCAL-00257\" summary=\"feat: Update workspace and translation files (main)&#10;&#10;This commit includes the following changes:&#10;&#10;- Updated the workspace configuration in .idea/workspace.xml to&#10;  reflect the recent npm script execution.&#10;- Modified the English translation file (translation.json) by&#10;  reordering the &quot;navbar&quot; keys.&#10;- Updated translation files in other languages with &quot;hireMe&quot;.\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1759503018626</created>\n      <option name=\"number\" value=\"00257\" />\n      <option name=\"presentableId\" value=\"LOCAL-00257\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1759503018627</updated>\n    </task>\n    <task id=\"LOCAL-00258\" summary=\"fix(i18n): Correct &quot;hireMe&quot; translation in navbar&#10;&#10;This commit corrects the translation of &quot;hireMe&quot; in the&#10;navigation bar across all supported languages. The order of&#10;the elements was also fixed to be consistent.\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1759503051936</created>\n      <option name=\"number\" value=\"00258\" />\n      <option name=\"presentableId\" value=\"LOCAL-00258\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1759503051936</updated>\n    </task>\n    <option name=\"localTasksCounter\" value=\"259\" />\n    <servers />\n  </component>\n  <component name=\"TypeScriptGeneratedFilesManager\">\n    <option name=\"version\" value=\"3\" />\n  </component>\n  <component name=\"Vcs.Log.History.Properties\">\n    <option name=\"COLUMN_ID_ORDER\">\n      <list>\n        <option value=\"Default.Root\" />\n        <option value=\"Default.Author\" />\n        <option value=\"Default.Date\" />\n        <option value=\"Default.Subject\" />\n        <option value=\"Space.CommitStatus\" />\n      </list>\n    </option>\n  </component>\n  <component name=\"Vcs.Log.Tabs.Properties\">\n    <option name=\"RECENT_FILTERS\">\n      <map>\n        <entry key=\"Branch\">\n          <value>\n            <list>\n              <RecentGroup>\n                <option name=\"FILTER_VALUES\">\n                  <option value=\"origin/main\" />\n                </option>\n              </RecentGroup>\n              <RecentGroup>\n                <option name=\"FILTER_VALUES\">\n                  <option value=\"origin/examples\" />\n                </option>\n              </RecentGroup>\n            </list>\n          </value>\n        </entry>\n      </map>\n    </option>\n    <option name=\"TAB_STATES\">\n      <map>\n        <entry key=\"MAIN\">\n          <value>\n            <State>\n              <option name=\"FILTERS\">\n                <map>\n                  <entry key=\"branch\">\n                    <value>\n                      <list>\n                        <option value=\"origin/main\" />\n                      </list>\n                    </value>\n                  </entry>\n                </map>\n              </option>\n            </State>\n          </value>\n        </entry>\n      </map>\n    </option>\n  </component>\n  <component name=\"VcsManagerConfiguration\">\n    <option name=\"CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT\" value=\"false\" />\n    <option name=\"CHECK_NEW_TODO\" value=\"false\" />\n    <option name=\"ADD_EXTERNAL_FILES_SILENTLY\" value=\"true\" />\n    <MESSAGE value=\"feat: Upgrade Node.js versions in CI (main)&#10;&#10;This commit updates the Node.js versions used in the CI&#10;workflows to version 20. This ensures that the CI&#10;environment uses a more up-to-date and supported version&#10;of Node.js.\" />\n    <MESSAGE value=\"feat\" />\n    <MESSAGE value=\"feat: Upgrade onnxruntime-web and onnxruntime-common (main)&#10;&#10;Up\" />\n    <MESSAGE value=\"feat: Upgrade onnxruntime-web and onnxruntime-common (main)&#10;&#10;Upgrades onnxruntime-web and onnxruntime-common to versions&#10;1\" />\n    <MESSAGE value=\"feat: Upgrade onnxruntime-web and onnxruntime-common (main)&#10;&#10;Upgrades onnxruntime-web and onnxruntime-common to versions&#10;1.23.0. This includes updates to dependencies and related&#10;packages.&#10;\" />\n    <MESSAGE value=\"feat: Upgrade onnxruntime-web and onnxruntime-common (main)&#10;&#10;Upgrades onnxruntime-web and onnxruntime-common to versions&#10;1.23.0. This includes updates to dependencies and related&#10;packages.\" />\n    <MESSAGE value=\"```&#10;feat: Replace &quot;Buy me a coffee&quot; with &quot;Hire me&quot; (main)&#10;&#10;\" />\n    <MESSAGE value=\"```&#10;feat: Replace &quot;Buy me a coffee&quot; with &quot;Hire me&quot; (main)&#10;&#10;This commit replaces the &quot;Buy me a coffee&quot; button with a&#10;&quot;Hire\" />\n    <MESSAGE value=\"```&#10;feat: Replace &quot;Buy me a coffee&quot; with &quot;Hire me&quot; (main)&#10;&#10;This commit replaces the &quot;Buy me a coffee&quot; button with a&#10;&quot;Hire me&quot; button in the navbar. It also updates the&#10;corresponding translation in the `en/translation.json` file.&#10;The icon has been changed to a\" />\n    <MESSAGE value=\"```&#10;feat: Replace &quot;Buy me a coffee&quot; with &quot;Hire me&quot; (main)&#10;&#10;This commit replaces the &quot;Buy me a coffee&quot; button with a&#10;&quot;Hire me&quot; button in the navbar. It also updates the&#10;corresponding translation in the `en/translation.json` file.&#10;The icon has been changed to a job search icon, and the&#10;link points to a Google Drive document.&#10;```\" />\n    <MESSAGE value=\"chore: Replace &quot;Buy me a coffee&quot; with &quot;Hire me&quot; (main)&#10;&#10;This commit replaces the &quot;Buy me a coffee&quot; button with a&#10;&quot;Hire me&quot; button in the navbar. It also updates the&#10;corresponding translation in the `en/translation.json` file.&#10;The icon has been changed to a job search icon, and the&#10;link points to a Google Drive document.\" />\n    <MESSAGE value=\"```&#10;feat: Update workspace and translation files (main)&#10;&#10;This commit includes the following changes:\" />\n    <MESSAGE value=\"```&#10;feat: Update workspace and translation files (main)&#10;&#10;This commit includes the following changes:&#10;&#10;- Updated the workspace configuration in .idea/workspace.xml to&#10;  reflect\" />\n    <MESSAGE value=\"```&#10;feat: Update workspace and translation files (main)&#10;&#10;This commit includes the following changes:&#10;&#10;- Updated the workspace configuration in .idea/workspace.xml to&#10;  reflect the recent npm script execution.&#10;- Modified the English translation file (translation.json) by&#10;  reordering the &quot;navbar&quot; keys.&#10;```&#10;\" />\n    <MESSAGE value=\"feat: Update workspace and translation files (main)&#10;&#10;This commit includes the following changes:&#10;&#10;- Updated the workspace configuration in .idea/workspace.xml to&#10;  reflect the recent npm script execution.&#10;- Modified the English translation file (translation.json) by&#10;  reordering the &quot;navbar&quot; keys.\" />\n    <MESSAGE value=\"```&#10;feat: Update workspace and translation files (main)&#10;&#10;This commit includes the following\" />\n    <MESSAGE value=\"```&#10;feat: Update workspace and translation files (main)&#10;&#10;This commit includes the following changes:&#10;&#10;- Updated the workspace configuration in .idea/workspace.xml to&#10;  \" />\n    <MESSAGE value=\"```&#10;feat: Update workspace and translation files (main)&#10;&#10;This commit includes the following changes:&#10;&#10;- Updated the workspace configuration in .idea/workspace.xml to&#10;  reflect the recent npm script execution.&#10;- Modified the English translation file (translation.json) by&#10;  reordering the &quot;navbar&quot; keys.&#10;-\" />\n    <MESSAGE value=\"```&#10;feat: Update workspace and translation files (main)&#10;&#10;This commit includes the following changes:&#10;&#10;- Updated the workspace configuration in .idea/workspace.xml to&#10;  reflect the recent npm script execution.&#10;- Modified the English translation file (translation.json) by&#10;  reordering the &quot;navbar&quot; keys.&#10;- Updated translation files in other languages with &quot;hireMe&quot;.&#10;```&#10;\" />\n    <MESSAGE value=\"feat: Update workspace and translation files (main)&#10;&#10;This commit includes the following changes:&#10;&#10;- Updated the workspace configuration in .idea/workspace.xml to&#10;  reflect the recent npm script execution.&#10;- Modified the English translation file (translation.json) by&#10;  reordering the &quot;navbar&quot; keys.&#10;- Updated translation files in other languages with &quot;hireMe&quot;.\" />\n    <MESSAGE value=\"```\" />\n    <MESSAGE value=\"```&#10;fix(i18n): Correct &quot;hireMe&quot; translation in navbar\" />\n    <MESSAGE value=\"```&#10;fix(i18n): Correct &quot;hireMe&quot; translation in navbar&#10;&#10;This commit corrects the translation of &quot;hireMe&quot; in the&#10;navigation bar\" />\n    <MESSAGE value=\"```&#10;fix(i18n): Correct &quot;hireMe&quot; translation in navbar&#10;&#10;This commit corrects the translation of &quot;hireMe&quot; in the&#10;navigation bar across all supported languages. The order of&#10;the elements was also fixed to be consistent.&#10;```&#10;\" />\n    <MESSAGE value=\"fix(i18n): Correct &quot;hireMe&quot; translation in navbar&#10;&#10;This commit corrects the translation of &quot;hireMe&quot; in the&#10;navigation bar across all supported languages. The order of&#10;the elements was also fixed to be consistent.\" />\n    <option name=\"LAST_COMMIT_MESSAGE\" value=\"fix(i18n): Correct &quot;hireMe&quot; translation in navbar&#10;&#10;This commit corrects the translation of &quot;hireMe&quot; in the&#10;navigation bar across all supported languages. The order of&#10;the elements was also fixed to be consistent.\" />\n  </component>\n  <component name=\"VgoProject\">\n    <integration-enabled>false</integration-enabled>\n    <settings-migrated>true</settings-migrated>\n  </component>\n  <component name=\"XSLT-Support.FileAssociations.UIState\">\n    <expand />\n    <select />\n  </component>\n</project>"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"trailingComma\": \"none\",\n  \"singleQuote\": true,\n  \"endOfLine\": \"auto\"\n}\n"
  },
  {
    "path": ".vitest/setup.ts",
    "content": "import '@testing-library/jest-dom/vitest';\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\"\n  },\n  \"files.associations\": {\n    \"*.css\": \"tailwindcss\"\n  }\n}\n"
  },
  {
    "path": "@types/i18n.d.ts",
    "content": "// types/i18next.d.ts\nimport 'i18next';\nimport { resources } from '../src/i18n';\n\ndeclare module 'i18next' {\n  interface CustomTypeOptions {\n    resources: (typeof resources)['en'];\n  }\n}\n"
  },
  {
    "path": "@types/theme.d.ts",
    "content": "import '@mui/material/styles';\n\ndeclare module '@mui/material/styles' {\n  interface TypeBackground {\n    hover?: string;\n    lightSecondary?: string;\n    darkSecondary?: string;\n  }\n}\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "* @iib0011 @Chesterkxng\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:20 AS build\n\nWORKDIR /app\n\nCOPY package.json package-lock.json ./\nRUN npm install\n\nCOPY . .\nRUN npm run build\n\nFROM nginx:alpine\n\nCOPY --from=build /app/dist /usr/share/nginx/html\n\nRUN sed -i 's/application\\/javascript.*js;/application\\/javascript                js mjs;/' /etc/nginx/mime.types\n\nRUN sed -i 's|index  index.html index.htm;|index  index.html index.htm;\\n        try_files $uri $uri/ /index.html;|' /etc/nginx/conf.d/default.conf\n\nEXPOSE 80\n\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 Ibrahima Gaye Coulibaly\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n        <img src=\"src/assets/logo.png\" width=\"220\" />\n        <br /><br />\n<a href=\"https://trendshift.io/repositories/13055\" target=\"_blank\"><img src=\"https://trendshift.io/api/badge/repositories/13055\" alt=\"iib0011%2Fomni-tools | Trendshift\" style=\"width: 200px;\" width=\"200\"/></a>\n   <br /><br />\n<a href=\"https://github.com/iib0011/omni-tools/releases\">\n          <img src=\"https://img.shields.io/badge/version-0.6.0-blue?style=for-the-badge\" />\n        </a>\n        <a href=\"https://hub.docker.com/r/iib0011/omni-tools\">\n          <img src=\"https://img.shields.io/docker/pulls/iib0011/omni-tools?style=for-the-badge&logo=docker\" />\n        </a>\n        <a href=\"https://github.com/iib0011\">\n          <img src=\"https://img.shields.io/github/stars/iib0011/omni-tools?style=for-the-badge&logo=github\" />\n        </a>\n        <a href=\"https://github.com/iib0011/omni-tools/blob/main/LICENSE\">\n          <img src=\"https://img.shields.io/github/license/iib0011/omni-tools?style=for-the-badge\" />\n        </a>\n        <a href=\"https://discord.gg/SDbbn3hT4b\">\n          <img src=\"https://img.shields.io/discord/1342971141823664179?label=Discord&style=for-the-badge\" />\n        </a>\n        <br /><br />\n</div>\n\nWelcome to OmniTools, a self-hosted web app offering a variety of online tools to simplify everyday tasks.\nWhether you are coding, manipulating images/videos, PDFs or crunching numbers, OmniTools has you covered. Please don't\nforget to\nstar the repo to support us.\nHere is the [demo](https://omnitools.app) website.\n\nAll files are processed entirely on the client side: nothing ever leaves your device.\nPlus, the Docker image is super lightweight at just 28MB, making it fast to deploy and easy to self-host.\n\n![img.png](docs-images/img.png)\n\n## Table of Contents\n\n- [Features](#features)\n- [Self-host](#self-hostrun)\n- [Contribute](#contribute)\n- [Contact](#contact)\n- [License](#license)\n\n## Features\n\nWe strive to offer a variety of tools, including:\n\n### **Image/Video/Audio Tools**\n\n- Image Resizer\n- Image Converter\n- Image Editor\n- Video Trimmer\n- Video Reverser\n- And more...\n\n### **PDF Tools**\n\n- PDF Splitter\n- PDF Merger\n- PDF Editor\n- And more...\n\n### **Text/List Tools**\n\n- Case Converters\n- List Shuffler\n- Text Formatters\n- And more...\n\n### **Date and Time Tools**\n\n- Date Calculators\n- Time Zone Converters\n- And more...\n\n### **Math Tools**\n\n- Generate Prime Numbers\n- Calculate voltage, current, or resistance\n- And more...\n\n### **Data Tools**\n\n- JSON Tools\n- CSV Tools\n- XML Tools\n- And more...\n\nStay tuned as we continue to expand and improve our collection!\n\n## Self-host/Run\n\n### Docker\n\n```bash\ndocker run -d --name omni-tools --restart unless-stopped -p 8080:80 iib0011/omni-tools:latest\n```\n\n### Docker Compose\n\n```yaml\nservices:\n  omni-tools:\n    image: iib0011/omni-tools:latest\n    container_name: omni-tools\n    restart: unless-stopped\n    ports:\n      - \"8080:80\"\n\n```\n\n## Contribute\n\nThis is a React Project with Typescript Material UI. We use icons from [Iconify](https://icon-sets.iconify.design)\n\n### Project setup\n\n```bash\ngit clone https://github.com/iib0011/omni-tools.git\ncd omni-tools\nnpm i\nnpm run dev\n```\n\n### Create a new tool\n\n```bash\nnpm run script:create:tool my-tool-name folder1 # npm run script:create:tool split pdf\n```\n\nFor tools located under multiple nested directories, use:\n\n```bash\nnpm run script:create:tool my-tool-name folder1/folder2 # npm run script:create:tool compress image/png\n```\n\nUse `folder1\\folder2` on Windows.\n\n### Run tests\n\n```bash\nnpm run test\n```\n\n- For e2e tests\n\n```bash\nnpm run test:e2e\n```\n\n### i18n (Translations)\nThe translation files are [here](public/locales). Only edit these if you are a developer. For non developers, use [Locize](https://www.locize.app/register?invitation=YOIH0Dyz3KHh3uQFCGYe9v1QOUoq8W5ySgmlwjX9cSypeJmt8F40brDtVbXb71fK).\n\n<img src=\"https://api.star-history.com/svg?repos=iib0011/omni-tools&type=Date\"/>\n\n## 🤝 Looking to contribute?\n\nWe welcome contributions! You can help by:\n\n- Reporting bugs\n- Suggesting new features in GitHub issues or [here](https://tally.so/r/nrkkx2)\n- Translating in [Locize project](https://www.locize.app/register?invitation=YOIH0Dyz3KHh3uQFCGYe9v1QOUoq8W5ySgmlwjX9cSypeJmt8F40brDtVbXb71fK).\n- Improving documentation\n- Submitting pull requests\n\n\nYou can also join our [Discord server](https://discord.gg/SDbbn3hT4b)\n## 🧡 Sponsors\n<div align=\"center\">\n  <a href=\"https://www.locize.com/\" target=\"_blank\">\n    <img src=\"docs-images/locizeSponsor.svg\" alt=\"Locize\" width=\"200\"/>\n  </a>\n</div>\n\nThanks to [Locize](https://www.locize.com) for sponsoring OmniTools and supporting localization efforts.\nThey make translation management simple and developer-friendly.\n\n## Contributors\n\n<a href=\"https://github.com/iib0011/omni-tools/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=iib0011/omni-tools\" />\n</a>\n\n## Contact\n\nFor any questions or suggestions, feel free to open an issue or contact me at:\n[ibracool99@gmail.com](mailto:ibracool99@gmail.com)\n\n## License\n\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n"
  },
  {
    "path": "commitlint.config.js",
    "content": "module.exports = { extends: ['@commitlint/config-conventional'] };\n"
  },
  {
    "path": "index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <link rel=\"icon\" type=\"image/png\" href=\"/favicon-96x96.png\" sizes=\"96x96\" />\n  <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\n  <link rel=\"shortcut icon\" href=\"/favicon.ico\" />\n  <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-touch-icon.png\" />\n  <meta name=\"apple-mobile-web-app-title\" content=\"OmniTools\" />\n  <link rel=\"manifest\" href=\"/site.webmanifest\" />\n  <link href=\"/assets/fonts/quicksand/quick-sand.css\" rel=\"stylesheet\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <title>OmniTools</title>\n</head>\n<body>\n<div id=\"root\"></div>\n<script type=\"module\" src=\"/src/index.tsx\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"omni-tools\",\n  \"description\": \"This project offers a variety of online tools to help with everyday tasks, \\nall available for free and open for community contributions\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"author\": {\n    \"name\": \"Ibrahima Gaye Coulibaly\",\n    \"email\": \"ibracool99@gmail.com\",\n    \"url\": \"https://github.com/iib0011\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/iib0011/omni-tools/issues\",\n    \"email\": \"ibracool99@gmail.com\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"serve\": \"vite preview\",\n    \"test\": \"vitest\",\n    \"test:e2e\": \"playwright test\",\n    \"test:ui\": \"vitest --ui\",\n    \"script:create:tool\": \"node scripts/create-tool.mjs\",\n    \"lint\": \"eslint src --max-warnings=0 --fix\",\n    \"typecheck\": \"tsc --project tsconfig.json --noEmit\",\n    \"prepare\": \"husky install\",\n    \"i18n:pull\": \"locize download --project-id e7156a3e-66fb-4035-a0f0-cebf1c63a3ba --path ./public/locales\",\n    \"i18n:sync\": \"locize sync --project-id e7156a3e-66fb-4035-a0f0-cebf1c63a3ba --path ./public/locales --update-values true --reference-language-only false -compare-modification-time true\"\n  },\n  \"dependencies\": {\n    \"@emotion/react\": \"^11.11.4\",\n    \"@emotion/styled\": \"^11.11.5\",\n    \"@ffmpeg/core\": \"^0.12.10\",\n    \"@ffmpeg/ffmpeg\": \"^0.12.15\",\n    \"@ffmpeg/util\": \"^0.12.2\",\n    \"@imgly/background-removal\": \"^1.6.0\",\n    \"@jimp/types\": \"^1.6.0\",\n    \"@monaco-editor/react\": \"^4.7.0\",\n    \"@mui/icons-material\": \"^5.15.20\",\n    \"@mui/material\": \"^5.15.20\",\n    \"@playwright/test\": \"^1.45.0\",\n    \"@simplepdf/react-embed-pdf\": \"^1.9.0\",\n    \"@types/ffmpeg\": \"^1.0.7\",\n    \"@types/js-quantities\": \"^1.6.6\",\n    \"@types/lodash\": \"^4.17.5\",\n    \"@types/morsee\": \"^1.0.2\",\n    \"@types/omggif\": \"^1.0.5\",\n    \"@types/react-i18next\": \"^7.8.3\",\n    \"browser-image-compression\": \"^2.0.2\",\n    \"buffer\": \"^6.0.3\",\n    \"color\": \"^4.2.3\",\n    \"cron-validator\": \"^1.3.1\",\n    \"cronstrue\": \"^3.0.0\",\n    \"dayjs\": \"^1.11.13\",\n    \"fast-xml-parser\": \"^5.2.5\",\n    \"formik\": \"^2.4.6\",\n    \"heic2any\": \"^0.0.4\",\n    \"i18next\": \"^25.3.2\",\n    \"i18next-browser-languagedetector\": \"^8.2.0\",\n    \"i18next-http-backend\": \"^3.0.2\",\n    \"jimp\": \"^0.22.12\",\n    \"js-quantities\": \"^1.8.0\",\n    \"jspdf\": \"^3.0.3\",\n    \"jszip\": \"^3.10.1\",\n    \"lint-staged\": \"^15.4.3\",\n    \"locize\": \"^4.0.14\",\n    \"lodash\": \"^4.17.21\",\n    \"mime\": \"^4.0.6\",\n    \"morsee\": \"^1.0.9\",\n    \"nerdamer-prime\": \"^1.2.4\",\n    \"notistack\": \"^3.0.1\",\n    \"omggif\": \"^1.0.10\",\n    \"onnxruntime-web\": \"1.21.0\",\n    \"pdf-lib\": \"^1.17.1\",\n    \"pdfjs-dist\": \"^5.2.133\",\n    \"playwright\": \"^1.45.0\",\n    \"qrcode\": \"^1.5.4\",\n    \"rc-slider\": \"^11.1.8\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"react-filerobot-image-editor\": \"^4.9.1\",\n    \"react-helmet\": \"^6.1.0\",\n    \"react-i18next\": \"^15.6.0\",\n    \"react-image-crop\": \"^11.0.7\",\n    \"react-konva\": \"^18.2.10\",\n    \"react-router-dom\": \"^6.23.1\",\n    \"styled-components\": \"^6.1.19\",\n    \"tesseract.js\": \"^6.0.0\",\n    \"type-fest\": \"^4.35.0\",\n    \"use-deep-compare-effect\": \"^1.8.1\",\n    \"yup\": \"^1.4.0\"\n  },\n  \"devDependencies\": {\n    \"@commitlint/cli\": \"^19.3.0\",\n    \"@commitlint/config-conventional\": \"^19.2.2\",\n    \"@iconify/react\": \"^5.2.0\",\n    \"@testing-library/jest-dom\": \"^6.4.5\",\n    \"@testing-library/react\": \"^14.3.1\",\n    \"@types/color\": \"^3.0.6\",\n    \"@types/color-rgba\": \"^2.1.2\",\n    \"@types/node\": \"^20.12.12\",\n    \"@types/qrcode\": \"^1.5.5\",\n    \"@types/react\": \"^18.3.3\",\n    \"@types/react-dom\": \"^18.3.0\",\n    \"@types/react-helmet\": \"^6.1.11\",\n    \"@types/trusted-types\": \"^1.0.6\",\n    \"@typescript-eslint/eslint-plugin\": \"^6.21.0\",\n    \"@typescript-eslint/parser\": \"^6.21.0\",\n    \"@vitejs/plugin-react-swc\": \"^3.7.0\",\n    \"@vitest/ui\": \"^1.6.0\",\n    \"autoprefixer\": \"^10.4.19\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"eslint-plugin-prettier\": \"^5.1.3\",\n    \"eslint-plugin-react\": \"^7.34.1\",\n    \"eslint-plugin-react-hooks\": \"^4.6.2\",\n    \"eslint-plugin-tailwindcss\": \"^3.17.0\",\n    \"happy-dom\": \"^12.10.3\",\n    \"husky\": \"^9.0.11\",\n    \"i18next-locize-backend\": \"^7.0.4\",\n    \"locize-cli\": \"^10.1.1\",\n    \"monaco-editor\": \"^0.53.0\",\n    \"postcss\": \"^8.4.38\",\n    \"prettier\": \"3.1.1\",\n    \"start-server-and-test\": \"^2.0.4\",\n    \"tailwindcss\": \"^3.4.3\",\n    \"typescript\": \"^5.4.5\",\n    \"vite\": \"^5.2.11\",\n    \"vite-tsconfig-paths\": \"^4.3.2\",\n    \"vitest\": \"^1.6.0\"\n  },\n  \"lint-staged\": {\n    \"*.{ts,tsx,js,jsx,json}\": \"prettier --write\"\n  }\n}\n"
  },
  {
    "path": "playwright.config.ts",
    "content": "import { defineConfig, devices } from '@playwright/test';\n\nexport default defineConfig({\n  testDir: './src',\n  testMatch: /\\.e2e\\.(spec\\.)?ts$/,\n  fullyParallel: true,\n  retries: 1,\n  reporter: [['html', { open: 'never' }]],\n  use: {\n    baseURL: 'http://localhost:4173',\n    trace: 'on-first-retry'\n  },\n  webServer: {\n    command: 'npm run build && npm run serve',\n    url: 'http://localhost:4173'\n  },\n  projects: [\n    {\n      name: 'chromium',\n      use: { ...devices['Desktop Chrome'] }\n    },\n    {\n      name: 'firefox',\n      use: { ...devices['Desktop Firefox'] }\n    },\n\n    {\n      name: 'webkit',\n      use: { ...devices['Desktop Safari'] }\n    }\n  ]\n});\n"
  },
  {
    "path": "postcss.config.mjs",
    "content": "export default {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {}\n  }\n};\n"
  },
  {
    "path": "public/_redirects",
    "content": "/*    /index.html   200\n"
  },
  {
    "path": "public/assets/fonts/quicksand/quick-sand.css",
    "content": "@font-face {\n  font-family: \"Quicksand\";\n  font-weight: 100 900;\n  font-display: swap;\n  font-style: normal;\n  font-named-instance: \"Regular\";\n  src: url(\"Quicksand-VariableFont_wght.ttf\");\n}\n\n@font-face {\n  font-family: \"Quicksand\";\n  font-weight: 100 900;\n  font-display: swap;\n  font-style: italic;\n  font-named-instance: \"Italic\";\n  src: url(\"quicksand-italic.ttf\");\n}\n"
  },
  {
    "path": "public/gs.js",
    "content": "// This is a placeholder file for the actual Ghostscript WASM implementation\n// In a real implementation, this would be the compiled Ghostscript WASM module\n\n// You would need to download the actual Ghostscript WASM files from:\n// https://github.com/ochachacha/ps2pdf-wasm or compile it yourself\n\n// This simulates the Module loading process that would occur with the real WASM file\n(function () {\n  // Simulate WASM loading\n  console.log('Loading Ghostscript WASM module...');\n\n  // Expose a simulated Module to the window\n  window.Module = window.Module || {};\n\n  // Simulate filesystem\n  window.FS = {\n    writeFile: function (name, data) {\n      console.log(`[Simulated] Writing file: ${name}`);\n      return true;\n    },\n    readFile: function (name, options) {\n      console.log(`[Simulated] Reading file: ${name}`);\n      // Return a sample Uint8Array that would represent a PDF\n      return new Uint8Array(10);\n    }\n  };\n\n  // Mark module as initialized after a delay to simulate loading\n  setTimeout(function () {\n    window.Module.calledRun = true;\n    console.log('Ghostscript WASM module loaded');\n\n    // Add callMain method for direct calling\n    window.Module.callMain = function (args) {\n      console.log('[Simulated] Running Ghostscript with args:', args);\n      // In a real implementation, this would execute the WASM module with the given arguments\n    };\n  }, 1000);\n})();\n"
  },
  {
    "path": "public/locales/de/audio.json",
    "content": "{\n  \"changeSpeed\": {\n    \"description\": \"Ändern Sie die Wiedergabegeschwindigkeit von Audiodateien. Beschleunigen oder verlangsamen Sie die Wiedergabe, ohne die Tonhöhe zu verändern.\",\n    \"inputTitle\": \"Audioeingang\",\n    \"newAudioSpeed\": \"Neue Audiogeschwindigkeit\",\n    \"outputFormat\": \"Ausgabeformat\",\n    \"resultTitle\": \"Bearbeitetes Audio\",\n    \"settingSpeed\": \"Geschwindigkeit einstellen\",\n    \"shortDescription\": \"Ändern Sie die Geschwindigkeit von Audiodateien\",\n    \"speedDescription\": \"Standardmultiplikator: 2 bedeutet 2x schneller\",\n    \"title\": \"Audiogeschwindigkeit ändern\",\n    \"toolInfo\": {\n      \"title\": \"Was ist {{title}}?\"\n    }\n  },\n  \"extractAudio\": {\n    \"description\": \"Extrahieren Sie Audiospuren aus Videodateien.\",\n    \"extractingAudio\": \"Audio extrahieren\",\n    \"inputTitle\": \"Eingangsvideo\",\n    \"outputFormat\": \"Ausgabeformat\",\n    \"outputFormatDescription\": \"Wählen Sie das Format für das zu extrahierende Audio aus.\",\n    \"resultTitle\": \"Extrahiertes Audio\",\n    \"shortDescription\": \"Extrahieren Sie Audio aus Videodateien (MP4, MOV usw.) in AAC, MP3 oder WAV.\",\n    \"title\": \"Audio aus Video extrahieren\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie die Audiospur aus Videodateien extrahieren. Sie können zwischen verschiedenen Audioformaten wählen, darunter AAC, MP3 und WAV.\",\n      \"title\": \"Was ist {{title}}?\"\n    }\n  },\n  \"mergeAudio\": {\n    \"description\": \"Kombinieren Sie mehrere Audiodateien zu einer einzigen Audiodatei, indem Sie sie der Reihe nach aneinanderreihen.\",\n    \"inputTitle\": \"Audiodateien eingeben\",\n    \"longDescription\": \"Mit diesem Tool können Sie mehrere Audiodateien in der Reihenfolge des Hochladens zu einer einzigen Datei zusammenfügen. Ideal zum Kombinieren von Podcast-Segmenten, Musiktiteln oder anderen Audiodateien. Unterstützt verschiedene Audioformate, darunter MP3, AAC und WAV.\",\n    \"mergingAudio\": \"Audio zusammenführen\",\n    \"outputFormat\": \"Ausgabeformat\",\n    \"resultTitle\": \"Zusammengeführtes Audio\",\n    \"shortDescription\": \"Mehrere Audiodateien zu einer zusammenführen (MP3, AAC, WAV).\",\n    \"title\": \"Audio zusammenführen\",\n    \"toolInfo\": {\n      \"title\": \"Was ist {{title}}?\"\n    }\n  },\n  \"trim\": {\n    \"description\": \"Schneiden und trimmen Sie Audiodateien, um bestimmte Segmente durch Angabe von Start- und Endzeiten zu extrahieren.\",\n    \"endTime\": \"Endzeit\",\n    \"endTimeDescription\": \"Endzeit im Format HH:MM:SS (z. B. 00:01:30)\",\n    \"inputTitle\": \"Audioeingang\",\n    \"longDescription\": \"Mit diesem Tool können Sie Audiodateien durch Festlegen von Start- und Endzeiten kürzen. Sie können bestimmte Abschnitte aus längeren Audiodateien extrahieren, unerwünschte Teile entfernen oder kürzere Clips erstellen. Unterstützt verschiedene Audioformate, darunter MP3, AAC und WAV. Ideal für Podcast-Bearbeitung, Musikproduktion und alle anderen Audiobearbeitungsaufgaben.\",\n    \"outputFormat\": \"Ausgabeformat\",\n    \"resultTitle\": \"Getrimmtes Audio\",\n    \"shortDescription\": \"Schneiden Sie Audiodateien, um bestimmte Zeitsegmente zu extrahieren (MP3, AAC, WAV).\",\n    \"startTime\": \"Startzeit\",\n    \"startTimeDescription\": \"Startzeit im Format HH:MM:SS (z. B. 00:00:30)\",\n    \"timeSettings\": \"Zeiteinstellungen\",\n    \"title\": \"Audio trimmen\",\n    \"toolInfo\": {\n      \"title\": \"Was ist {{title}}?\"\n    },\n    \"trimmingAudio\": \"Audio trimmen\"\n  }\n}\n"
  },
  {
    "path": "public/locales/de/converters.json",
    "content": "{\n  \"audioConverter\": {\n    \"title\": \"Audio-Konverter\",\n    \"description\": \"Konvertieren Sie Audiodateien zwischen verschiedenen Formaten.\",\n    \"shortDescription\": \"Konvertieren Sie Audiodateien in verschiedene Formate.\",\n    \"longDescription\": \"Dieses Tool ermöglicht es Ihnen, Audiodateien von einem Format in ein anderes zu konvertieren und unterstützt eine breite Palette von Audioformaten für eine nahtlose Konvertierung.\",\n    \"outputFormat\": \"Ausgabeformat\",\n    \"outputFormatDescription\": \"Wählen Sie das gewünschte Audioausgabeformat\",\n    \"inputTitle\": \"Audioeingabe\",\n    \"outputTitle\": \"Konvertierte Audio\"\n  }\n}\n"
  },
  {
    "path": "public/locales/de/csv.json",
    "content": "{\n  \"changeCsvSeparator\": {\n    \"description\": \"Ändern Sie das Trennzeichen in CSV-Dateien. Konvertieren Sie zwischen verschiedenen CSV-Formaten wie Komma, Semikolon, Tabulator oder benutzerdefinierten Trennzeichen.\",\n    \"shortDescription\": \"CSV-Dateitrennzeichen ändern\",\n    \"title\": \"CSV-Trennzeichen ändern\"\n  },\n  \"csvRowsToColumns\": {\n    \"description\": \"Dieses Tool konvertiert Zeilen einer CSV-Datei (Comma Separated Values) in Spalten. Es extrahiert die horizontalen Zeilen einzeln aus der CSV-Eingabedatei, dreht sie um 90 Grad und gibt sie als vertikale Spalten nacheinander, durch Kommas getrennt, aus.', longDescription: 'Dieses Tool konvertiert Zeilen einer CSV-Datei (Comma Separated Values) in Spalten. Wenn die CSV-Eingabedaten beispielsweise sechs Zeilen haben, enthält die Ausgabe sechs Spalten, und die Elemente der Zeilen werden von oben nach unten angeordnet. In einer korrekt formatierten CSV-Datei ist die Anzahl der Werte in jeder Zeile gleich. Fehlende Felder in Zeilen können vom Programm korrigiert werden. Sie haben die Wahl zwischen verschiedenen Optionen: Füllen Sie fehlende Daten mit leeren Elementen auf oder ersetzen Sie fehlende Daten durch benutzerdefinierte Elemente wie \\\"missing\\\", \\\"?\\\" oder \\\"x\\\". Während der Konvertierung bereinigt das Tool die CSV-Datei außerdem von unnötigen Informationen wie Leerzeilen (Zeilen ohne sichtbare Informationen) und Kommentaren. Damit das Tool Kommentare korrekt erkennt, können Sie in den Optionen das Symbol am Zeilenanfang angeben, mit dem ein Kommentar beginnt. Dieses Symbol ist typischerweise ein Rautezeichen (#) oder ein doppelter Schrägstrich (//). CSV-abulous!\",\n    \"longDescription\": \"Dieses Tool konvertiert Zeilen einer CSV-Datei (Comma Separated Values) in Spalten. Wenn die CSV-Eingabedaten beispielsweise sechs Zeilen umfassen, enthält die Ausgabe sechs Spalten, und die Elemente der Zeilen werden von oben nach unten angeordnet. In einer korrekt formatierten CSV-Datei ist die Anzahl der Werte in jeder Zeile gleich. Sollten in Zeilen jedoch Felder fehlen, kann das Programm diese korrigieren. Sie haben die Wahl zwischen verschiedenen Optionen: Füllen Sie fehlende Daten mit leeren Elementen auf oder ersetzen Sie fehlende Daten durch benutzerdefinierte Elemente, wie z. B.\",\n    \"shortDescription\": \"Konvertieren Sie CSV-Zeilen in Spalten.\",\n    \"title\": \"CSV-Zeilen in Spalten konvertieren\"\n  },\n  \"csvToJson\": {\n    \"columnSeparator\": \"Spaltentrennzeichen (z. B. , ; \\\\t)\",\n    \"commentSymbol\": \"Kommentarsymbol (z. B. #)\",\n    \"conversionOptions\": \"Konvertierungsoptionen\",\n    \"description\": \"Konvertieren Sie CSV-Dateien in das JSON-Format mit anpassbaren Optionen für Trennzeichen, Anführungszeichen und Ausgabeformatierung. Unterstützt werden Header, Kommentare und dynamische Typkonvertierung.\",\n    \"dynamicTypes\": \"Dynamische Typen\",\n    \"dynamicTypesDescription\": \"Automatische Konvertierung von Zahlen und Booleschen Werten\",\n    \"error\": \"Fehler\",\n    \"errorParsing\": \"Fehler beim Parsen der CSV-Datei: {{error}}\",\n    \"fieldQuote\": \"Feld Zitat (z. B. \\\")\",\n    \"inputCsvFormat\": \"Eingabe-CSV-Format\",\n    \"inputTitle\": \"Eingabe-CSV\",\n    \"invalidCsvFormat\": \"Ungültiges CSV-Format\",\n    \"resultTitle\": \"JSON-Ausgabe\",\n    \"shortDescription\": \"Konvertieren Sie CSV-Daten in das JSON-Format.\",\n    \"skipEmptyLines\": \"Leere Zeilen überspringen\",\n    \"skipEmptyLinesDescription\": \"Leere Zeilen in der CSV-Eingabe ignorieren\",\n    \"title\": \"Konvertieren Sie CSV in JSON\",\n    \"toolInfo\": {\n      \"description\": \"Dieses Tool konvertiert CSV-Dateien (Comma Separated Values) in JSON-Datenstrukturen (JavaScript Object Notation). Es unterstützt verschiedene CSV-Formate mit anpassbaren Trennzeichen, Anführungszeichen und Kommentarsymbolen. Der Konverter kann die erste Zeile als Überschrift behandeln, Leerzeilen überspringen und Datentypen wie Zahlen und Boolesche Werte automatisch erkennen. Das resultierende JSON kann für Datenmigration, Backups oder als Eingabe für andere Anwendungen verwendet werden.\",\n      \"title\": \"Was ist ein CSV-zu-JSON-Konverter?\"\n    },\n    \"useHeaders\": \"Verwenden Sie Überschriften\",\n    \"useHeadersDescription\": \"Behandeln Sie die erste Zeile als Spaltenüberschrift\"\n  },\n  \"csvToTsv\": {\n    \"description\": \"Laden Sie Ihre CSV-Datei im untenstehenden Formular hoch. Sie wird automatisch in eine TSV-Datei konvertiert. In den Tool-Optionen können Sie das CSV-Eingabeformat anpassen – Feldtrennzeichen, Anführungszeichen und Kommentarsymbol festlegen, leere CSV-Zeilen überspringen und festlegen, ob CSV-Spaltenüberschriften beibehalten werden sollen.\",\n    \"longDescription\": \"Dieses Tool wandelt CSV-Daten (Comma Separated Values) in TSV-Daten (Tab Separated Values) um. Sowohl CSV als auch TSV sind gängige Dateiformate zur Speicherung tabellarischer Daten, verwenden jedoch unterschiedliche Trennzeichen zur Trennung der Werte – CSV verwendet Kommas (\",\n    \"shortDescription\": \"Konvertieren Sie CSV-Daten in das TSV-Format.\",\n    \"title\": \"Konvertieren Sie CSV in TSV\"\n  },\n  \"csvToXml\": {\n    \"description\": \"Konvertieren Sie CSV-Dateien mit anpassbaren Optionen in das XML-Format.\",\n    \"shortDescription\": \"Konvertieren Sie CSV-Daten in das XML-Format.\",\n    \"title\": \"Konvertieren Sie CSV in XML\"\n  },\n  \"csvToYaml\": {\n    \"description\": \"Laden Sie Ihre CSV-Datei einfach im untenstehenden Formular hoch. Sie wird dann automatisch in eine YAML-Datei konvertiert. In den Tool-Optionen können Sie Feldtrennzeichen, Anführungszeichen und Kommentarzeichen festlegen, um das Tool an benutzerdefinierte CSV-Formate anzupassen. Zusätzlich können Sie das YAML-Ausgabeformat auswählen: eines, das CSV-Header beibehält, oder eines, das CSV-Header ausschließt.\",\n    \"longDescription\": \"Dieses Tool wandelt CSV-Daten (Comma Separated Values) in YAML-Daten (Yet Another Markup Language) um. CSV ist ein einfaches, tabellarisches Format zur Darstellung matrixartiger Datentypen mit Zeilen und Spalten. YAML hingegen ist ein erweitertes Format (eigentlich eine Obermenge von JSON), das besser lesbare Daten für die Serialisierung erzeugt und Listen, Wörterbücher und verschachtelte Objekte unterstützt. Das Programm unterstützt verschiedene CSV-Eingabeformate – die Eingabedaten können durch Kommas (Standard), Semikolons, Pipes oder ein anderes Trennzeichen getrennt sein. Sie können das genaue Trennzeichen Ihrer Daten in den Optionen festlegen. Ebenso können Sie in den Optionen das Anführungszeichen festlegen, das zum Umschließen von CSV-Feldern verwendet wird (standardmäßig ein doppeltes Anführungszeichen). Sie können auch Zeilen überspringen, die mit Kommentaren beginnen, indem Sie die Kommentarsymbole in den Optionen angeben. So halten Sie Ihre Daten übersichtlich, indem Sie unnötige Zeilen überspringen. Es gibt zwei Möglichkeiten, CSV in YAML zu konvertieren. Die erste Methode konvertiert jede CSV-Zeile in eine YAML-Liste. Die zweite Methode extrahiert Header aus der ersten CSV-Zeile und erstellt YAML-Objekte mit Schlüsseln basierend auf diesen Headern. Sie können das YAML-Ausgabeformat auch anpassen, indem Sie die Anzahl der Leerzeichen für die Einrückung von YAML-Strukturen angeben. Wenn Sie die umgekehrte Konvertierung durchführen, d. h. YAML in CSV umwandeln möchten, können Sie unser Tool „YAML in CSV konvertieren“ verwenden. CSV-genial!\",\n    \"shortDescription\": \"Konvertieren Sie schnell eine CSV-Datei in eine YAML-Datei.\",\n    \"title\": \"Konvertieren Sie CSV in YAML\"\n  },\n  \"findIncompleteCsvRecords\": {\n    \"checkingOptions\": \"Optionen prüfen\",\n    \"commentCharacterDescription\": \"Geben Sie das Zeichen ein, das den Beginn einer Kommentarzeile kennzeichnet. Zeilen, die mit diesem Symbol beginnen, werden übersprungen.\",\n    \"csvInputOptions\": \"CSV-Eingabeoptionen\",\n    \"csvSeparatorDescription\": \"Geben Sie das Zeichen ein, das zum Trennen von Spalten in der CSV-Eingabedatei verwendet wird.\",\n    \"deleteLinesWithNoData\": \"Zeilen ohne Daten löschen\",\n    \"deleteLinesWithNoDataDescription\": \"Entfernen Sie leere Zeilen aus der CSV-Eingabedatei.\",\n    \"description\": \"Laden Sie einfach Ihre CSV-Datei im untenstehenden Formular hoch. Das Tool prüft automatisch, ob in Zeilen und Spalten Werte fehlen. In den Tool-Optionen können Sie das Eingabedateiformat anpassen (Trennzeichen, Anführungszeichen und Kommentarzeichen festlegen). Zusätzlich können Sie die Prüfung auf leere Werte aktivieren, leere Zeilen überspringen und die Anzahl der Fehlermeldungen in der Ausgabe begrenzen.\",\n    \"findEmptyValues\": \"Leere Werte finden\",\n    \"findEmptyValuesDescription\": \"Zeigt eine Meldung zu leeren CSV-Feldern an (das sind keine fehlenden Felder, sondern Felder, die nichts enthalten).\",\n    \"inputTitle\": \"Eingabe-CSV\",\n    \"limitNumberOfMessages\": \"Anzahl der Nachrichten begrenzen\",\n    \"messageLimitDescription\": \"Legen Sie die Begrenzung der Anzahl der Nachrichten in der Ausgabe fest.\",\n    \"quoteCharacterDescription\": \"Geben Sie das Anführungszeichen ein, das zum Zitieren der CSV-Eingabefelder verwendet wird.\",\n    \"resultTitle\": \"CSV-Status\",\n    \"shortDescription\": \"Finden Sie schnell Zeilen und Spalten in CSV, in denen Werte fehlen.\",\n    \"title\": \"Unvollständige CSV-Datensätze finden\",\n    \"toolInfo\": {\n      \"title\": \"Was ist ein {{title}}?\"\n    }\n  },\n  \"insertCsvColumns\": {\n    \"appendColumns\": \"Spalten anhängen\",\n    \"commentCharacterDescription\": \"Geben Sie das Zeichen ein, das den Beginn einer Kommentarzeile kennzeichnet. Zeilen, die mit diesem Symbol beginnen, werden übersprungen.\",\n    \"csvOptions\": \"CSV-Optionen\",\n    \"csvSeparator\": \"CSV-Trennzeichen\",\n    \"csvToInsert\": \"CSV zum Einfügen\",\n    \"csvToInsertDescription\": \"Geben Sie eine oder mehrere Spalten ein, die Sie in die CSV-Datei einfügen möchten. Das Zeichen zur Spaltentrennung muss mit dem Zeichen in der CSV-Eingabedatei übereinstimmen. Hinweis: Leere Zeilen werden ignoriert.\",\n    \"customFillDescription\": \"Wenn die CSV-Eingabedatei unvollständig ist (fehlende Werte), fügen Sie den Datensätzen dann leere Felder oder benutzerdefinierte Symbole hinzu, um eine wohlgeformte CSV-Datei zu erstellen?\",\n    \"customFillValueDescription\": \"Verwenden Sie diesen benutzerdefinierten Wert, um fehlende Felder auszufüllen. (Funktioniert nur mit dem oben beschriebenen Modus „Benutzerdefinierte Werte“).\",\n    \"customPosition\": \"Benutzerdefinierte Position\",\n    \"customPositionOptionsDescription\": \"Wählen Sie die Methode zum Einfügen der Spalten in die CSV-Datei.\",\n    \"description\": \"Fügen Sie den CSV-Daten an angegebenen Positionen neue Spalten hinzu.\",\n    \"fillWithCustomValues\": \"Mit Zollwerten füllen\",\n    \"fillWithEmptyValues\": \"Mit leeren Werten füllen\",\n    \"headerName\": \"Kopfzeilenname\",\n    \"headerNameDescription\": \"Überschrift der Spalte, nach der Sie Spalten einfügen möchten.\",\n    \"inputTitle\": \"Eingabe-CSV\",\n    \"insertingPositionDescription\": \"Geben Sie an, wo die Spalten in der CSV-Datei eingefügt werden sollen.\",\n    \"position\": \"Position\",\n    \"positionOptions\": \"Positionsoptionen\",\n    \"prependColumns\": \"Spalten voranstellen\",\n    \"quoteCharDescription\": \"Geben Sie das Anführungszeichen ein, das zum Zitieren der CSV-Eingabefelder verwendet wird.\",\n    \"resultTitle\": \"Ausgabe-CSV\",\n    \"rowNumberDescription\": \"Nummer der Spalte, nach der Sie Spalten einfügen möchten.\",\n    \"separatorDescription\": \"Geben Sie das Zeichen ein, das zum Trennen von Spalten in der CSV-Eingabedatei verwendet wird.\",\n    \"shortDescription\": \"Fügen Sie schnell eine oder mehrere neue Spalten an beliebiger Stelle in einer CSV-Datei ein.\",\n    \"title\": \"CSV-Spalten einfügen\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie neue Spalten an bestimmten Positionen in CSV-Daten einfügen. Sie können Spalten basierend auf Überschriftennamen oder Spaltennummern an benutzerdefinierten Positionen voranstellen, anhängen oder einfügen.\",\n      \"title\": \"CSV-Spalten einfügen\"\n    }\n  },\n  \"swapCsvColumns\": {\n    \"description\": \"Laden Sie einfach Ihre CSV-Datei im untenstehenden Formular hoch und geben Sie die zu tauschenden Spalten an. Das Tool ändert dann automatisch die Positionen der angegebenen Spalten in der Ausgabedatei. In den Tool-Optionen können Sie die zu tauschenden Spaltenpositionen oder -namen angeben, unvollständige Daten korrigieren und optional leere und auskommentierte Datensätze entfernen.\",\n    \"longDescription\": \"Dieses Tool reorganisiert CSV-Daten, indem es die Positionen der Spalten tauscht. Das Tauschen von Spalten verbessert die Lesbarkeit einer CSV-Datei, indem häufig verwendete Daten zusammen oder nach vorne platziert werden, um den Vergleich und die Bearbeitung zu erleichtern. Sie können beispielsweise die erste Spalte mit der letzten oder die zweite Spalte mit der dritten tauschen. Um Spalten basierend auf ihren Positionen zu tauschen, wählen Sie das\",\n    \"shortDescription\": \"CSV-Spalten neu anordnen.\",\n    \"title\": \"CSV-Spalten austauschen\"\n  },\n  \"transposeCsv\": {\n    \"description\": \"Laden Sie einfach Ihre CSV-Datei in das untenstehende Formular hoch. Das Tool transponiert Ihre CSV-Datei automatisch. In den Tool-Optionen können Sie das Zeichen angeben, mit dem die Kommentarzeilen in der CSV-Datei beginnen, um diese zu entfernen. Falls die CSV-Datei unvollständig ist (fehlende Werte), können Sie fehlende Werte durch ein Leerzeichen oder ein benutzerdefiniertes Zeichen ersetzen.\",\n    \"longDescription\": \"Dieses Tool transponiert Komma-getrennte Werte (CSV). Es behandelt die CSV-Datei wie eine Datenmatrix und spiegelt alle Elemente entlang der Hauptdiagonale. Die Ausgabe enthält dieselben CSV-Daten wie die Eingabe, wobei nun alle Zeilen zu Spalten und alle Spalten zu Zeilen werden. Nach der Transposition weist die CSV-Datei entgegengesetzte Dimensionen auf. Wenn die Eingabedatei beispielsweise 4 Spalten und 3 Zeilen enthält, enthält die Ausgabedatei 3 Spalten und 4 Zeilen. Während der Konvertierung bereinigt das Programm die Daten außerdem von unnötigen Zeilen und korrigiert unvollständige Daten. Insbesondere löscht das Tool automatisch alle leeren Datensätze und Kommentare, die mit einem bestimmten Zeichen beginnen, das Sie in den Optionen festlegen können. Sollten die CSV-Daten beschädigt oder verloren gehen, ergänzt das Dienstprogramm die Datei zusätzlich mit leeren Feldern oder benutzerdefinierten Feldern, die Sie in den Optionen festlegen können. CSV-unverständlich!\",\n    \"shortDescription\": \"Transponieren Sie schnell eine CSV-Datei.\",\n    \"title\": \"CSV transponieren\"\n  },\n  \"tsvToJson\": {\n    \"description\": \"Konvertieren Sie TSV-Daten (Tab-Separated Values) in das JSON-Format. Transformieren Sie tabellarische Daten in strukturierte JSON-Objekte.\",\n    \"shortDescription\": \"Konvertieren Sie TSV in das JSON-Format\",\n    \"title\": \"TSV zu JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/de/image.json",
    "content": "{\n  \"changeColors\": {\n    \"description\": \"Welt\",\n    \"shortDescription\": \"Schnelles Austauschen der Farben in einem Bild\",\n    \"title\": \"Farben im Bild ändern\"\n  },\n  \"changeOpacity\": {\n    \"description\": \"Passen Sie die Transparenz Ihrer Bilder ganz einfach an. Laden Sie einfach Ihr Bild hoch, stellen Sie mit dem Schieberegler die gewünschte Deckkraft zwischen 0 (vollständig transparent) und 1 (vollständig undurchsichtig) ein und laden Sie das geänderte Bild herunter.\",\n    \"shortDescription\": \"Transparenz von Bildern anpassen\",\n    \"title\": \"Bilddeckkraft ändern\"\n  },\n  \"compress\": {\n    \"compressedSize\": \"Komprimierte Größe\",\n    \"compressionOptions\": \"Komprimierungsoptionen\",\n    \"description\": \"Reduzieren Sie die Bilddateigröße, ohne die Qualität zu beeinträchtigen.\",\n    \"failedToCompress\": \"Das Komprimieren des Bilds ist fehlgeschlagen. Bitte versuchen Sie es erneut.\",\n    \"fileSizes\": \"Dateigrößen\",\n    \"inputTitle\": \"Eingabebild\",\n    \"maxFileSizeDescription\": \"Maximale Dateigröße in Megabyte\",\n    \"originalSize\": \"Originalgröße\",\n    \"qualityDescription\": \"Bildqualität in Prozent (niedriger bedeutet kleinere Dateigröße)\",\n    \"resultTitle\": \"Komprimiertes Bild\",\n    \"shortDescription\": \"Komprimieren Sie Bilder, um die Dateigröße zu reduzieren und gleichzeitig eine angemessene Qualität beizubehalten.\",\n    \"title\": \"Bild komprimieren\"\n  },\n  \"compressPng\": {\n    \"description\": \"Dies ist ein Programm zum Komprimieren von PNG-Bildern. Sobald Sie Ihr PNG-Bild in den Eingabebereich einfügen, komprimiert das Programm es und zeigt das Ergebnis im Ausgabebereich an. In den Optionen können Sie den Komprimierungsgrad anpassen und die alte und neue Bilddateigröße einsehen.\",\n    \"shortDescription\": \"PNG schnell komprimieren\",\n    \"title\": \"PNG komprimieren\"\n  },\n  \"convertJgpToPng\": {\n    \"description\": \"Konvertieren Sie Ihre JPG-Bilder schnell in PNG. Importieren Sie einfach Ihr PNG-Bild im Editor links\",\n    \"shortDescription\": \"Konvertieren Sie Ihre JPG-Bilder schnell in PNG\",\n    \"title\": \"Konvertieren Sie JPG in PNG\"\n  },\n  \"convertToJpg\": {\n    \"description\": \"Konvertieren Sie verschiedene Bildformate (PNG, GIF, TIF, PSD, SVG, WEBP, HEIC, RAW) in JPG mit anpassbaren Qualitäts- und Hintergrundfarbeinstellungen.\",\n    \"shortDescription\": \"Konvertieren Sie Bilder mit Qualitätskontrolle in JPG\",\n    \"title\": \"Bilder in JPG konvertieren\"\n  },\n  \"createTransparent\": {\n    \"description\": \"Welt\",\n    \"shortDescription\": \"Machen Sie ein Bild schnell transparent\",\n    \"title\": \"Transparentes PNG erstellen\"\n  },\n  \"crop\": {\n    \"description\": \"Schneiden Sie Bilder zu, um unerwünschte Bereiche zu entfernen.\",\n    \"inputTitle\": \"Eingabebild\",\n    \"resultTitle\": \"Zugeschnittenes Bild\",\n    \"shortDescription\": \"Bilder schnell zuschneiden.\",\n    \"title\": \"Bild zuschneiden\"\n  },\n  \"editor\": {\n    \"description\": \"Erweiterter Bildeditor mit Werkzeugen zum Zuschneiden, Drehen, Kommentieren, Anpassen von Farben und Hinzufügen von Wasserzeichen. Bearbeiten Sie Ihre Bilder mit professionellen Werkzeugen direkt in Ihrem Browser.\",\n    \"shortDescription\": \"Bearbeiten Sie Bilder mit erweiterten Tools und Funktionen\",\n    \"title\": \"Bildeditor\"\n  },\n  \"imageToText\": {\n    \"description\": \"Extrahieren Sie Text aus Bildern (JPG, PNG) mithilfe der optischen Zeichenerkennung (OCR).\",\n    \"shortDescription\": \"Extrahieren Sie mithilfe von OCR Text aus Bildern.\",\n    \"title\": \"Bild zu Text (OCR)\"\n  },\n  \"qrCode\": {\n    \"description\": \"Generieren Sie QR-Codes für verschiedene Datentypen: URL, Text, E-Mail, Telefon, SMS, WLAN, vCard und mehr.\",\n    \"shortDescription\": \"Erstellen Sie individuelle QR-Codes für verschiedene Datenformate.\",\n    \"title\": \"QR-Code-Generator\"\n  },\n  \"removeBackground\": {\n    \"description\": \"Welt\",\n    \"shortDescription\": \"Hintergründe automatisch aus Bildern entfernen\",\n    \"title\": \"Hintergrund aus Bild entfernen\"\n  },\n  \"resize\": {\n    \"description\": \"Passen Sie die Größe von Bildern an andere Abmessungen an.\",\n    \"dimensionType\": \"Dimensionstyp\",\n    \"heightDescription\": \"Höhe (in Pixeln)\",\n    \"inputTitle\": \"Eingabebild\",\n    \"maintainAspectRatio\": \"Seitenverhältnis beibehalten\",\n    \"maintainAspectRatioDescription\": \"Behalten Sie das ursprüngliche Seitenverhältnis des Bildes bei.\",\n    \"percentage\": \"Prozentsatz\",\n    \"percentageDescription\": \"Prozentsatz der Originalgröße (z. B. 50 für halbe Größe, 200 für doppelte Größe)\",\n    \"resizeByPercentage\": \"Größe um Prozent ändern\",\n    \"resizeByPercentageDescription\": \"Ändern Sie die Größe, indem Sie einen Prozentsatz der Originalgröße angeben.\",\n    \"resizeByPixels\": \"Größe in Pixeln ändern\",\n    \"resizeByPixelsDescription\": \"Ändern Sie die Größe, indem Sie die Abmessungen in Pixeln angeben.\",\n    \"resizeMethod\": \"Größenänderungsmethode\",\n    \"resultTitle\": \"Bildgröße geändert\",\n    \"setHeight\": \"Höhe festlegen\",\n    \"setHeightDescription\": \"Geben Sie die Höhe in Pixeln an und berechnen Sie die Breite basierend auf dem Seitenverhältnis.\",\n    \"setWidth\": \"Breite festlegen\",\n    \"setWidthDescription\": \"Geben Sie die Breite in Pixeln an und berechnen Sie die Höhe basierend auf dem Seitenverhältnis.\",\n    \"shortDescription\": \"Ändern Sie die Größe von Bildern ganz einfach.\",\n    \"title\": \"Bildgröße ändern\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie die Größe von JPG-, PNG-, SVG- und GIF-Bildern ändern. Sie können die Größe ändern, indem Sie die Abmessungen in Pixeln oder Prozent angeben und das ursprüngliche Seitenverhältnis beibehalten.\",\n      \"title\": \"Bildgröße ändern\"\n    },\n    \"widthDescription\": \"Breite (in Pixeln)\"\n  },\n  \"rotate\": {\n    \"description\": \"Drehen Sie ein Bild um einen bestimmten Winkel.\",\n    \"shortDescription\": \"Drehen Sie ein Bild ganz einfach.\",\n    \"title\": \"Bild drehen\"\n  }\n}\n"
  },
  {
    "path": "public/locales/de/json.json",
    "content": "{\n  \"comparison\": {\n    \"description\": \"Vergleichen Sie zwei JSON-Objekte, um Unterschiede in Struktur und Werten zu erkennen.\",\n    \"shortDescription\": \"Unterschiede zwischen zwei JSON-Objekten finden\",\n    \"title\": \"JSON vergleichen\"\n  },\n  \"escapeJson\": {\n    \"description\": \"Escapen Sie Sonderzeichen in JSON-Strings. Konvertieren Sie JSON-Daten in ein korrekt maskiertes Format für eine sichere Übertragung oder Speicherung.\",\n    \"shortDescription\": \"Escape-Sonderzeichen in JSON\",\n    \"title\": \"Escape-JSON\"\n  },\n  \"jsonToXml\": {\n    \"description\": \"Konvertieren Sie JSON-Daten in das XML-Format. Transformieren Sie strukturierte JSON-Objekte in wohlgeformte XML-Dokumente.\",\n    \"shortDescription\": \"Konvertieren Sie JSON in das XML-Format\",\n    \"title\": \"JSON zu XML\"\n  },\n  \"minify\": {\n    \"description\": \"Entfernen Sie alle unnötigen Leerzeichen aus JSON.\",\n    \"inputTitle\": \"JSON-Eingabe\",\n    \"resultTitle\": \"Minimiertes JSON\",\n    \"shortDescription\": \"Minimieren Sie JSON durch Entfernen von Leerzeichen\",\n    \"title\": \"JSON minimieren\",\n    \"toolInfo\": {\n      \"description\": \"Bei der JSON-Minimierung werden alle unnötigen Leerzeichen aus JSON-Daten entfernt, ohne dass deren Gültigkeit verloren geht. Dazu gehören Leerzeichen, Zeilenumbrüche und Einrückungen, die für die korrekte JSON-Analyse nicht erforderlich sind. Durch die Minimierung wird die Größe von JSON-Daten reduziert, wodurch die Speicherung und Übertragung effizienter wird, während die Datenstruktur und die Werte unverändert bleiben.\",\n      \"title\": \"Was ist JSON-Minimierung?\"\n    }\n  },\n  \"prettify\": {\n    \"description\": \"Formatieren Sie JSON mit den richtigen Einrückungen und Abständen.\",\n    \"indentation\": \"Vertiefung\",\n    \"inputTitle\": \"JSON-Eingabe\",\n    \"resultTitle\": \"Verschönertes JSON\",\n    \"shortDescription\": \"Formatieren und Verschönern von JSON-Code\",\n    \"title\": \"JSON verschönern\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie JSON-Daten mit den richtigen Einrückungen und Abständen formatieren, sodass sie besser lesbar und einfacher zu bearbeiten sind.\",\n      \"title\": \"JSON verschönern\"\n    },\n    \"useSpaces\": \"Räume verwenden\",\n    \"useSpacesDescription\": \"Ausgabe mit Leerzeichen einrücken\",\n    \"useTabs\": \"Verwenden von Registerkarten\",\n    \"useTabsDescription\": \"Einrücken der Ausgabe mit Tabulatoren.\"\n  },\n  \"stringify\": {\n    \"description\": \"Konvertieren Sie JavaScript-Objekte in das JSON-String-Format. Serialisieren Sie Datenstrukturen zur Speicherung oder Übertragung in JSON-Strings.\",\n    \"shortDescription\": \"Konvertieren Sie Objekte in JSON-Zeichenfolgen\",\n    \"title\": \"Stringify JSON\"\n  },\n  \"validateJson\": {\n    \"description\": \"Überprüfen Sie, ob JSON gültig und wohlgeformt ist.\",\n    \"inputTitle\": \"JSON-Eingabe\",\n    \"invalidJson\": \"❌ {{error}}\",\n    \"resultTitle\": \"Validierungsergebnis\",\n    \"shortDescription\": \"JSON-Code auf Fehler überprüfen\",\n    \"title\": \"JSON validieren\",\n    \"toolInfo\": {\n      \"description\": \"JSON (JavaScript Object Notation) ist ein einfaches Datenaustauschformat. Die JSON-Validierung stellt sicher, dass die Datenstruktur dem JSON-Standard entspricht. Ein gültiges JSON-Objekt muss Folgendes enthalten: – Eigenschaftsnamen in doppelten Anführungszeichen. – Ausgewogene geschweifte Klammern {}. – Keine abschließenden Kommas nach dem letzten Schlüssel-Wert-Paar. – Korrekte Verschachtelung von Objekten und Arrays. Dieses Tool prüft das eingegebene JSON und gibt Feedback, um häufige Fehler zu identifizieren und zu beheben.\",\n      \"title\": \"Was ist JSON-Validierung?\"\n    },\n    \"validJson\": \"✅ Gültiges JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/de/list.json",
    "content": "{\n  \"duplicate\": {\n    \"concatenate\": \"Verketten\",\n    \"concatenateDescription\": \"Kopien verketten (wenn nicht aktiviert, werden die Elemente miteinander verwoben)\",\n    \"copyDescription\": \"Anzahl der Kopien (kann Bruchteile sein)\",\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Duplizieren von Listeneinträgen. Geben Sie Ihre Liste ein und legen Sie die Duplizierungskriterien fest, um Kopien von Einträgen zu erstellen. Ideal für Datenerweiterungen, Tests oder die Erstellung wiederkehrender Muster.\",\n    \"duplicationOptions\": \"Duplizierungsoptionen\",\n    \"error\": \"Fehler\",\n    \"example1Description\": \"Dieses Beispiel zeigt, wie eine Liste von Wörtern dupliziert wird.\",\n    \"example1Title\": \"Einfache Vervielfältigung\",\n    \"example2Description\": \"Dieses Beispiel zeigt, wie eine Liste in umgekehrter Reihenfolge dupliziert wird.\",\n    \"example2Title\": \"Rückwärtsduplizierung\",\n    \"example3Description\": \"Dieses Beispiel zeigt, wie Elemente miteinander verwoben werden, anstatt sie zu verketten.\",\n    \"example3Title\": \"Verweben von Gegenständen\",\n    \"example4Description\": \"Dieses Beispiel zeigt, wie eine Liste mit einer Bruchzahl von Kopien dupliziert wird.\",\n    \"example4Title\": \"Bruchduplizierung\",\n    \"examples\": {\n      \"fractional\": {\n        \"description\": \"Dieses Beispiel zeigt, wie eine Liste mit einer Bruchzahl von Kopien dupliziert wird.\",\n        \"title\": \"Bruchduplizierung\"\n      },\n      \"interweave\": {\n        \"description\": \"Dieses Beispiel zeigt, wie Elemente miteinander verwoben werden, anstatt sie zu verketten.\",\n        \"title\": \"Verweben von Gegenständen\"\n      },\n      \"reverse\": {\n        \"description\": \"Dieses Beispiel zeigt, wie eine Liste in umgekehrter Reihenfolge dupliziert wird.\",\n        \"title\": \"Rückwärtsduplizierung\"\n      },\n      \"simple\": {\n        \"description\": \"Dieses Beispiel zeigt, wie eine Liste von Wörtern dupliziert wird.\",\n        \"title\": \"Einfache Vervielfältigung\"\n      }\n    },\n    \"inputTitle\": \"Eingabeliste\",\n    \"joinSeparatorDescription\": \"Trennzeichen zum Verbinden der duplizierten Liste\",\n    \"resultTitle\": \"Duplizierte Liste\",\n    \"reverse\": \"Umkehren\",\n    \"reverseDescription\": \"Umkehren der duplizierten Elemente\",\n    \"shortDescription\": \"Doppelte Listenelemente mit angegebenen Kriterien\",\n    \"splitByRegex\": \"Aufteilen nach regulärem Ausdruck\",\n    \"splitBySymbol\": \"Nach Symbol teilen\",\n    \"splitOptions\": \"Teilungsoptionen\",\n    \"splitSeparatorDescription\": \"Trennzeichen zum Teilen der Liste\",\n    \"title\": \"Duplikat\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie Elemente einer Liste duplizieren. Sie können die Anzahl der Kopien (einschließlich Bruchzahlen) festlegen, steuern, ob Elemente verknüpft oder verwoben werden, und sogar die Duplikate umkehren. Es ist nützlich, um wiederkehrende Muster zu erstellen, Testdaten zu generieren oder Listen mit vorhersehbarem Inhalt zu erweitern.\",\n      \"title\": \"Listenduplizierung\"\n    },\n    \"unknownError\": \"Ein unbekannter Fehler ist aufgetreten\",\n    \"validation\": {\n      \"copyMustBeNumber\": \"Die Anzahl der Kopien muss eine Zahl sein\",\n      \"copyMustBePositive\": \"Die Anzahl der Kopien muss positiv sein\",\n      \"copyRequired\": \"Anzahl der Kopien ist erforderlich\",\n      \"joinSeparatorRequired\": \"Der Join-Separator ist erforderlich\",\n      \"separatorRequired\": \"Das Trennzeichen ist erforderlich\"\n    }\n  },\n  \"findMostPopular\": {\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Auffinden der beliebtesten Elemente in einer Liste. Geben Sie Ihre Liste ein und erhalten Sie sofort die am häufigsten vorkommenden Elemente. Ideal für Datenanalysen, Trenderkennung oder die Suche nach gemeinsamen Elementen.\",\n    \"displayFormatDescription\": \"Wie werden die beliebtesten Listenelemente angezeigt?\",\n    \"displayOptions\": {\n      \"count\": \"Anzahl der Elemente anzeigen\",\n      \"percentage\": \"Artikelprozentsatz anzeigen\",\n      \"total\": \"Artikelsumme anzeigen\"\n    },\n    \"extractListItems\": \"Wie extrahiere ich Listenelemente?\",\n    \"ignoreItemCase\": \"Groß-/Kleinschreibung ignorieren\",\n    \"ignoreItemCaseDescription\": \"Vergleichen Sie alle Listenelemente in Kleinbuchstaben.\",\n    \"inputTitle\": \"Eingabeliste\",\n    \"itemComparison\": \"Artikelvergleich\",\n    \"outputFormat\": \"Ausgabeformat für Top-Elemente\",\n    \"removeEmptyItems\": \"Leere Elemente entfernen\",\n    \"removeEmptyItemsDescription\": \"Leere Elemente beim Vergleich ignorieren.\",\n    \"resultTitle\": \"Die beliebtesten Artikel\",\n    \"shortDescription\": \"Am häufigsten vorkommende Elemente finden\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Alphabetisch sortieren\",\n      \"count\": \"Nach Anzahl sortieren\"\n    },\n    \"sortingMethodDescription\": \"Wählen Sie eine Sortiermethode aus.\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Begrenzen Sie die Elemente der Eingabeliste mit einem regulären Ausdruck.\",\n        \"title\": \"Verwenden Sie einen regulären Ausdruck zum Aufteilen\"\n      },\n      \"symbol\": {\n        \"description\": \"Begrenzen Sie die Elemente der Eingabeliste durch ein Zeichen.\",\n        \"title\": \"Verwenden Sie ein Symbol zum Teilen\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Legen Sie ein Trennsymbol oder einen regulären Ausdruck fest.\",\n    \"title\": \"Finden Sie die beliebtesten\",\n    \"trimItems\": \"Top-Listenelemente kürzen\",\n    \"trimItemsDescription\": \"Entfernen Sie führende und nachfolgende Leerzeichen, bevor Sie Elemente vergleichen\"\n  },\n  \"findUnique\": {\n    \"caseSensitiveItems\": \"Groß- und Kleinschreibung beachten\",\n    \"caseSensitiveItemsDescription\": \"Geben Sie Elemente mit unterschiedlicher Groß-/Kleinschreibung als eindeutige Elemente in der Liste aus.\",\n    \"delimiterDescription\": \"Legen Sie ein Trennsymbol oder einen regulären Ausdruck fest.\",\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Auffinden eindeutiger Elemente in einer Liste. Geben Sie Ihre Liste ein und erhalten Sie sofort alle eindeutigen Werte, ohne Duplikate. Ideal für Datenbereinigung, Deduplizierung oder das Auffinden eindeutiger Elemente.\",\n    \"findAbsolutelyUniqueItems\": \"Finden Sie absolut einzigartige Artikel\",\n    \"findAbsolutelyUniqueItemsDescription\": \"Zeigen Sie nur die Elemente der Liste an, die in einer einzigen Kopie vorhanden sind.\",\n    \"inputListDelimiter\": \"Eingabelisten-Trennzeichen\",\n    \"inputTitle\": \"Eingabeliste\",\n    \"outputListDelimiter\": \"Ausgabelisten-Trennzeichen\",\n    \"resultTitle\": \"Einzigartige Gegenstände\",\n    \"shortDescription\": \"Suchen Sie nach eindeutigen Elementen in einer Liste\",\n    \"skipEmptyItems\": \"Leere Elemente überspringen\",\n    \"skipEmptyItemsDescription\": \"Schließen Sie die leeren Listenelemente nicht in die Ausgabe ein.\",\n    \"title\": \"Finden Sie einzigartige\",\n    \"trimItems\": \"Listenelemente kürzen\",\n    \"trimItemsDescription\": \"Entfernen Sie vor dem Vergleichen von Elementen führende und nachfolgende Leerzeichen.\",\n    \"uniqueItemOptions\": \"Einzigartige Artikeloptionen\"\n  },\n  \"group\": {\n    \"deleteEmptyItems\": \"Leere Elemente löschen\",\n    \"deleteEmptyItemsDescription\": \"Ignorieren Sie leere Elemente und nehmen Sie sie nicht in die Gruppen auf.\",\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Gruppieren von Listenelementen. Geben Sie Ihre Liste ein und legen Sie Gruppierungskriterien fest, um Elemente in logische Gruppen zu ordnen. Ideal zum Kategorisieren von Daten, Organisieren von Informationen oder Erstellen strukturierter Listen. Unterstützt benutzerdefinierte Trennzeichen und verschiedene Gruppierungsoptionen.\",\n    \"emptyItemsAndPadding\": \"Leere Elemente und Polsterung\",\n    \"groupNumberDescription\": \"Anzahl der Elemente in einer Gruppe\",\n    \"groupSeparatorDescription\": \"Gruppentrennzeichen\",\n    \"groupSizeAndSeparators\": \"Gruppengröße und Trennzeichen\",\n    \"inputItemSeparator\": \"Eingabeelement-Trennzeichen\",\n    \"inputTitle\": \"Eingabeliste\",\n    \"itemSeparatorDescription\": \"Elementtrennzeichen\",\n    \"leftWrapDescription\": \"Linkes Umbruchsymbol der Gruppe.\",\n    \"padNonFullGroups\": \"Nicht volle Gruppen auffüllen\",\n    \"padNonFullGroupsDescription\": \"Füllen Sie nicht volle Gruppen mit einem benutzerdefinierten Element (unten eingeben).\",\n    \"paddingCharDescription\": \"Verwenden Sie dieses Zeichen oder Element, um nicht volle Gruppen aufzufüllen.\",\n    \"resultTitle\": \"Gruppierte Elemente\",\n    \"rightWrapDescription\": \"Rechtes Umbruchsymbol der Gruppe.\",\n    \"shortDescription\": \"Gruppieren Sie Listenelemente nach gemeinsamen Eigenschaften\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Begrenzen Sie die Elemente der Eingabeliste mit einem regulären Ausdruck.\",\n        \"title\": \"Verwenden Sie einen regulären Ausdruck zum Aufteilen\"\n      },\n      \"symbol\": {\n        \"description\": \"Begrenzen Sie die Elemente der Eingabeliste durch ein Zeichen.\",\n        \"title\": \"Verwenden Sie ein Symbol zum Teilen\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Legen Sie ein Trennsymbol oder einen regulären Ausdruck fest.\",\n    \"title\": \"Gruppe\"\n  },\n  \"reverse\": {\n    \"description\": \"Diese einfache browserbasierte Anwendung druckt alle Listenelemente rückwärts. Die Eingabeelemente können durch ein beliebiges Symbol getrennt werden. Sie können auch das Trennzeichen der umgekehrten Listenelemente ändern.\",\n    \"inputTitle\": \"Eingabeliste\",\n    \"itemSeparator\": \"Elementtrennzeichen\",\n    \"itemSeparatorDescription\": \"Legen Sie ein Trennsymbol oder einen regulären Ausdruck fest.\",\n    \"outputListOptions\": \"Ausgabelistenoptionen\",\n    \"outputSeparatorDescription\": \"Trennzeichen für Ausgabelistenelemente.\",\n    \"resultTitle\": \"Umgekehrte Liste\",\n    \"shortDescription\": \"Schnelles Umkehren einer Liste\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Begrenzen Sie die Elemente der Eingabeliste mit einem regulären Ausdruck.\",\n        \"title\": \"Verwenden Sie einen regulären Ausdruck zum Aufteilen\"\n      },\n      \"symbol\": {\n        \"description\": \"Begrenzen Sie die Elemente der Eingabeliste durch ein Zeichen.\",\n        \"title\": \"Verwenden Sie ein Symbol zum Teilen\"\n      }\n    },\n    \"splitterMode\": \"Splitter-Modus\",\n    \"title\": \"Umkehren\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Dienstprogramm können Sie die Reihenfolge der Elemente in einer Liste umkehren. Das Dienstprogramm zerlegt die Eingabeliste zunächst in einzelne Elemente und durchläuft diese dann vom letzten bis zum ersten Element. Dabei wird jedes Element ausgegeben. Die Eingabeliste kann alles enthalten, was sich als Text darstellen lässt, z. B. Ziffern, Zahlen, Zeichenfolgen, Wörter, Sätze usw. Das Trennzeichen für die Eingabeelemente kann auch ein regulärer Ausdruck sein. Beispielsweise ermöglicht der reguläre Ausdruck /[;,]/ die Verwendung von Elementen, die entweder durch Kommas oder Semikolons getrennt sind. Die Trennzeichen für die Elemente der Eingabe- und Ausgabeliste können in den Optionen angepasst werden. Standardmäßig sind sowohl die Eingabe- als auch die Ausgabelisten durch Kommas getrennt. Listabulous!\",\n      \"title\": \"Was ist ein Listenumkehrer?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Rotieren von Listenelementen. Geben Sie Ihre Liste ein und legen Sie den Rotationsgrad fest, um die Elemente um eine bestimmte Anzahl von Positionen zu verschieben. Ideal für Datenmanipulation, zyklische Verschiebungen oder das Neuordnen von Listen.\",\n    \"shortDescription\": \"Listenelemente um angegebene Positionen rotieren\",\n    \"title\": \"Drehen\"\n  },\n  \"shuffle\": {\n    \"delimiterDescription\": \"Legen Sie ein Trennsymbol oder einen regulären Ausdruck fest.\",\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Mischen von Listenelementen. Geben Sie Ihre Liste ein und erhalten Sie sofort eine zufällige Version mit Elementen in zufälliger Reihenfolge. Perfekt, um Abwechslung zu schaffen, Zufälligkeit zu testen oder geordnete Daten zu vermischen.\",\n    \"inputListSeparator\": \"Eingabelistentrennzeichen\",\n    \"inputTitle\": \"Eingabeliste\",\n    \"joinSeparatorDescription\": \"Verwenden Sie dieses Trennzeichen in der zufälligen Liste.\",\n    \"outputLengthDescription\": \"So viele zufällige Elemente ausgeben\",\n    \"resultTitle\": \"Gemischte Liste\",\n    \"shortDescription\": \"Zufällige Reihenfolge der Listenelemente\",\n    \"shuffledListLength\": \"Länge der gemischten Liste\",\n    \"shuffledListSeparator\": \"Trennzeichen für gemischte Listen\",\n    \"title\": \"Shuffle\"\n  },\n  \"sort\": {\n    \"caseSensitive\": \"Groß-/Kleinschreibung beachten\",\n    \"caseSensitiveDescription\": \"Groß- und Kleinbuchstaben werden getrennt sortiert. Großbuchstaben stehen in aufsteigender Reihenfolge vor Kleinbuchstaben. (Funktioniert nur im alphabetischen Sortiermodus.)\",\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Sortieren von Listenelementen. Geben Sie Ihre Liste ein und legen Sie Sortierkriterien fest, um die Elemente in auf- oder absteigender Reihenfolge zu sortieren. Ideal für die Datenorganisation, Textverarbeitung oder das Erstellen sortierter Listen.\",\n    \"inputItemSeparator\": \"Trennzeichen für Eingabeelemente\",\n    \"inputTitle\": \"Eingabeliste\",\n    \"joinSeparatorDescription\": \"Verwenden Sie dieses Symbol als Verbindung zwischen Elementen in einer sortierten Liste.\",\n    \"orderDescription\": \"Wählen Sie eine Sortierreihenfolge aus.\",\n    \"orderOptions\": {\n      \"decreasing\": \"Absteigende Reihenfolge\",\n      \"increasing\": \"Zunehmende Ordnung\"\n    },\n    \"removeDuplicates\": \"Duplikate entfernen\",\n    \"removeDuplicatesDescription\": \"Löschen Sie doppelte Listenelemente.\",\n    \"resultTitle\": \"Sortierte Liste\",\n    \"shortDescription\": \"Listenelemente in angegebener Reihenfolge sortieren\",\n    \"sortMethod\": \"Sortiermethode\",\n    \"sortMethodDescription\": \"Wählen Sie eine Sortiermethode aus.\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Alphabetisch sortieren\",\n      \"length\": \"Nach Länge sortieren\",\n      \"numeric\": \"Numerisch sortieren\"\n    },\n    \"sortedItemProperties\": \"Sortierte Artikeleigenschaften\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Begrenzen Sie die Elemente der Eingabeliste mit einem regulären Ausdruck.\",\n        \"title\": \"Verwenden Sie einen regulären Ausdruck zum Aufteilen\"\n      },\n      \"symbol\": {\n        \"description\": \"Trennen Sie die Elemente der Eingabeliste durch ein Zeichen.\",\n        \"title\": \"Verwenden Sie ein Symbol zum Teilen\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Legen Sie ein Trennsymbol oder einen regulären Ausdruck fest.\",\n    \"title\": \"Sortieren\"\n  },\n  \"truncate\": {\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Kürzen von Listen. Geben Sie Ihre Liste ein und legen Sie die maximale Anzahl der zu behaltenden Elemente fest. Ideal für die Datenverarbeitung, Listenverwaltung oder die Begrenzung der Inhaltslänge.\",\n    \"shortDescription\": \"Liste auf die angegebene Anzahl von Elementen kürzen\",\n    \"title\": \"Kürzen\"\n  },\n  \"unwrap\": {\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Entpacken von Listenelementen. Geben Sie Ihre verpackte Liste ein und legen Sie Entpackkriterien fest, um organisierte Elemente zu vereinfachen. Ideal für die Datenverarbeitung, Textbearbeitung oder das Extrahieren von Inhalten aus strukturierten Listen.\",\n    \"shortDescription\": \"Listenelemente aus strukturiertem Format auspacken\",\n    \"title\": \"Auspacken\"\n  },\n  \"wrap\": {\n    \"description\": \"Fügen Sie vor und nach jedem Listenelement Text hinzu.\",\n    \"inputTitle\": \"Eingabeliste\",\n    \"joinSeparatorDescription\": \"Trennzeichen zum Verbinden der umschlossenen Liste\",\n    \"leftTextDescription\": \"Vor jedem Element hinzuzufügender Text\",\n    \"removeEmptyItems\": \"Leere Elemente entfernen\",\n    \"resultTitle\": \"Umbrochene Liste\",\n    \"rightTextDescription\": \"Nach jedem Element hinzuzufügender Text\",\n    \"shortDescription\": \"Listenelemente mit angegebenen Kriterien umbrechen\",\n    \"splitByRegex\": \"Aufteilen nach regulärem Ausdruck\",\n    \"splitBySymbol\": \"Nach Symbol teilen\",\n    \"splitOptions\": \"Teilungsoptionen\",\n    \"splitSeparatorDescription\": \"Trennzeichen zum Teilen der Liste\",\n    \"title\": \"Wickeln\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie vor und nach jedem Listenelement Text hinzufügen. Sie können für die linke und rechte Seite unterschiedlichen Text festlegen und die Verarbeitung der Liste steuern. Es ist nützlich, um Listenelemente mit Anführungszeichen, Klammern oder anderen Formatierungen zu versehen, Daten für verschiedene Formate vorzubereiten oder strukturierten Text zu erstellen.\",\n      \"title\": \"Listenumbruch\"\n    },\n    \"wrapOptions\": \"Wrap-Optionen\"\n  }\n}\n"
  },
  {
    "path": "public/locales/de/number.json",
    "content": "{\n  \"arithmeticSequence\": {\n    \"commonDifferenceDescription\": \"Gemeinsamer Unterschied zwischen Begriffen (d)\",\n    \"description\": \"Generieren Sie arithmetische Folgen mit anpassbaren Parametern.\",\n    \"firstTermDescription\": \"Erster Term der Folge (a₁)\",\n    \"numberOfTermsDescription\": \"Anzahl der zu generierenden Terme (n)\",\n    \"outputFormat\": \"Ausgabeformat\",\n    \"resultTitle\": \"Generierte Sequenz\",\n    \"separatorDescription\": \"Trennzeichen zwischen Begriffen\",\n    \"sequenceParameters\": \"Sequenzparameter\",\n    \"shortDescription\": \"Arithmetische Folgen generieren\",\n    \"title\": \"Arithmetische Folge\",\n    \"toolInfo\": {\n      \"description\": \"Eine arithmetische Folge ist eine Zahlenfolge, bei der die Differenz zwischen den aufeinanderfolgenden Termen konstant ist. Diese konstante Differenz wird als gemeinsame Differenz bezeichnet. Gegeben sind der erste Term (a₁) und die gemeinsame Differenz (d). Jeder Term kann durch Addition der gemeinsamen Differenz zum vorherigen Term ermittelt werden.\",\n      \"title\": \"Was ist eine arithmetische Folge?\"\n    }\n  },\n  \"generate\": {\n    \"arithmeticSequenceOption\": \"Arithmetische Sequenzoption\",\n    \"description\": \"Generieren Sie eine Zahlenfolge mit anpassbaren Parametern.\",\n    \"numberOfElementsDescription\": \"Anzahl der Elemente in der Sequenz.\",\n    \"resultTitle\": \"Generierte Zahlen\",\n    \"separator\": \"Separator\",\n    \"separatorDescription\": \"Trennen Sie Elemente in der arithmetischen Folge durch dieses Zeichen.\",\n    \"shortDescription\": \"Generieren Sie Zufallszahlen in angegebenen Bereichen\",\n    \"startSequenceDescription\": \"Starten Sie die Sequenz ab dieser Nummer.\",\n    \"stepDescription\": \"Erhöhen Sie jedes Element um diesen Betrag\",\n    \"title\": \"Erzeugen\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie eine Zahlenfolge mit anpassbaren Parametern generieren. Sie können den Startwert, die Schrittweite und die Anzahl der Elemente festlegen.\",\n      \"title\": \"Zahlen generieren\"\n    }\n  },\n  \"ohmsLaw\": {\n    \"description\": \"Berechnet Spannung, Strom und Widerstand\",\n    \"longDescription\": \"Dieser Rechner wendet das Ohmsche Gesetz (V = I × R) an, um drei elektrische Parameter zu bestimmen, wenn die beiden anderen bekannt sind. Das Ohmsche Gesetz ist ein grundlegendes Prinzip der Elektrotechnik und beschreibt die Beziehung zwischen Spannung (V), Stromstärke (I) und Widerstand (R). Dieses Tool ist unverzichtbar für Elektronikbastler, Elektroingenieure und Studierende, die mit Schaltkreisen arbeiten, um unbekannte Werte in ihren elektrischen Konstruktionen schnell zu berechnen.\",\n    \"shortDescription\": \"Berechnen Sie Spannung, Strom oder Widerstand in Stromkreisen mit dem Ohmschen Gesetz\",\n    \"title\": \"Ohmsches Gesetz\"\n  },\n  \"randomNumberGenerator\": {\n    \"description\": \"Generieren Sie Zufallszahlen innerhalb eines angegebenen Bereichs mit anpassbaren Optionen.\",\n    \"error\": {\n      \"generationFailed\": \"Zufallszahlen konnten nicht generiert werden. Bitte überprüfen Sie Ihre Eingabeparameter.\"\n    },\n    \"info\": {\n      \"description\": \"Ein Zufallszahlengenerator erzeugt unvorhersehbare Zahlen innerhalb eines festgelegten Bereichs. Dieses Tool verwendet kryptografisch sichere Zufallszahlengenerierung, um wirklich zufällige Ergebnisse zu gewährleisten. Nützlich für Simulationen, Spiele, statistische Stichproben und Testszenarien.\",\n      \"title\": \"Was ist ein Zufallszahlengenerator?\"\n    },\n    \"longDescription\": \"Generieren Sie Zufallszahlen innerhalb eines festgelegten Bereichs mit Optionen für Ganzzahlen oder Dezimalzahlen, erlauben oder verhindern Sie Duplikate und sortieren Sie die Ergebnisse. Ideal für Simulationen, Tests, Spiele und statistische Analysen.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDecimals\": {\n          \"description\": \"Generieren Sie Dezimalzahlen anstelle von Ganzzahlen\",\n          \"title\": \"Dezimalzahlen zulassen\"\n        },\n        \"allowDuplicates\": {\n          \"description\": \"Erlauben Sie, dass dieselbe Nummer mehrmals erscheint\",\n          \"title\": \"Duplikate zulassen\"\n        },\n        \"countDescription\": \"Anzahl der zu generierenden Zufallszahlen (1-10.000)\",\n        \"sortResults\": {\n          \"description\": \"Sortieren Sie die generierten Zahlen in aufsteigender Reihenfolge\",\n          \"title\": \"Ergebnisse sortieren\"\n        },\n        \"title\": \"Generierungsoptionen\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Zeichen zum Trennen der generierten Zahlen\",\n        \"title\": \"Ausgabeeinstellungen\"\n      },\n      \"range\": {\n        \"maxDescription\": \"Maximalwert (einschließlich)\",\n        \"minDescription\": \"Mindestwert (einschließlich)\",\n        \"title\": \"Bereichseinstellungen\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Zählen\",\n      \"hasDuplicates\": \"Enthält Duplikate\",\n      \"isSorted\": \"Sortiert\",\n      \"range\": \"Reichweite\",\n      \"title\": \"Generierte Zufallszahlen\"\n    },\n    \"shortDescription\": \"Generieren Sie Zufallszahlen in benutzerdefinierten Bereichen\",\n    \"title\": \"Zufallszahlengenerator\"\n  },\n  \"randomPortGenerator\": {\n    \"description\": \"Generieren Sie zufällige Netzwerkports innerhalb angegebener Bereiche mit anpassbaren Optionen.\",\n    \"error\": {\n      \"generationFailed\": \"Fehler beim Generieren zufälliger Ports. Bitte überprüfen Sie Ihre Eingabeparameter.\"\n    },\n    \"info\": {\n      \"description\": \"Ein Zufallsgenerator für Ports erstellt unvorhersehbare Netzwerk-Portnummern innerhalb festgelegter Bereiche. Dieses Tool folgt den IANA-Portnummernstandards und ermöglicht die Identifizierung gängiger Dienste. Nützlich für Entwicklung, Tests, Netzwerkkonfiguration und zur Vermeidung von Portkonflikten.\",\n      \"title\": \"Was ist ein Zufallsgenerator?\"\n    },\n    \"longDescription\": \"Generiert zufällige Netzwerkports innerhalb festgelegter Bereiche (bekannt, registriert, dynamisch oder benutzerdefiniert). Ideal für Entwicklung, Tests und Netzwerkkonfiguration. Inklusive Port-Service-Identifikation für gängige Ports.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDuplicates\": {\n          \"description\": \"Mehrfaches Erscheinen desselben Ports zulassen\",\n          \"title\": \"Duplikate zulassen\"\n        },\n        \"countDescription\": \"Anzahl der zu generierenden zufälligen Ports (1–1.000)\",\n        \"sortResults\": {\n          \"description\": \"Sortieren Sie die generierten Ports in aufsteigender Reihenfolge\",\n          \"title\": \"Ergebnisse sortieren\"\n        },\n        \"title\": \"Generierungsoptionen\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Zeichen zum Trennen der generierten Ports\",\n        \"title\": \"Ausgabeeinstellungen\"\n      },\n      \"range\": {\n        \"custom\": \"Benutzerdefinierter Bereich\",\n        \"dynamic\": \"Dynamische Ports (49152-65535)\",\n        \"maxPortDescription\": \"Maximale Portnummer (1-65535)\",\n        \"minPortDescription\": \"Minimale Portnummer (1-65535)\",\n        \"registered\": \"Registrierte Ports (1024-49151)\",\n        \"title\": \"Portbereichseinstellungen\",\n        \"wellKnown\": \"Bekannte Ports (1-1023)\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Zählen\",\n      \"hasDuplicates\": \"Enthält Duplikate\",\n      \"isSorted\": \"Sortiert\",\n      \"portDetails\": \"Hafendetails\",\n      \"range\": \"Portbereich\",\n      \"title\": \"Generierte zufällige Ports\"\n    },\n    \"shortDescription\": \"Zufällige Netzwerkports generieren\",\n    \"title\": \"Zufalls-Port-Generator\"\n  },\n  \"slackline\": {\n    \"description\": \"Berechnet die Spannung einer Slackline\",\n    \"longDescription\": \"Dieser Rechner geht von einer Last in der Mitte des Seils aus\",\n    \"shortDescription\": \"Berechne die ungefähre Spannung einer Slackline oder Wäscheleine. Verlasse dich aus Sicherheitsgründen nicht darauf.\",\n    \"title\": \"Slackline-Spannung\"\n  },\n  \"sphereArea\": {\n    \"description\": \"Fläche einer Kugel\",\n    \"longDescription\": \"Dieser Rechner berechnet die Oberfläche einer Kugel mit der Formel A = 4πr². Sie können entweder den Radius eingeben, um die Oberfläche zu berechnen, oder die Oberfläche eingeben, um den gewünschten Radius zu berechnen. Dieses Tool ist nützlich für Geometriestudenten, Ingenieure, die mit sphärischen Objekten arbeiten, und alle, die Berechnungen mit sphärischen Oberflächen durchführen müssen.\",\n    \"shortDescription\": \"Berechnen Sie die Oberfläche einer Kugel anhand ihres Radius\",\n    \"title\": \"Fläche einer Kugel\"\n  },\n  \"sphereVolume\": {\n    \"description\": \"Volumen einer Kugel\",\n    \"longDescription\": \"Dieser Rechner berechnet das Volumen einer Kugel mit der Formel V = (4/3)πr³. Sie können entweder den Radius oder den Durchmesser eingeben, um das Volumen zu ermitteln, oder das Volumen eingeben, um den gewünschten Radius zu bestimmen. Das Tool ist nützlich für Studierende, Ingenieure und Fachleute, die mit kugelförmigen Objekten in Bereichen wie Physik, Ingenieurwesen und Fertigung arbeiten.\",\n    \"shortDescription\": \"Berechnen Sie das Volumen einer Kugel anhand des Radius oder Durchmessers\",\n    \"title\": \"Volumen einer Kugel\"\n  },\n  \"sum\": {\n    \"description\": \"Berechnen Sie die Summe einer Zahlenliste. Geben Sie die Zahlen durch Kommas oder Zeilenumbrüche getrennt ein, um die Gesamtsumme zu erhalten.\",\n    \"example1Description\": \"In diesem Beispiel berechnen wir die Summe von zehn positiven Ganzzahlen. Diese Ganzzahlen werden als Spalte aufgelistet und ihre Gesamtsumme beträgt 19494.\",\n    \"example1Title\": \"Summe von zehn positiven Zahlen\",\n    \"example2Description\": \"Dieses Beispiel kehrt eine Spalte mit zwanzig dreisilbigen Nomen um und gibt alle Wörter von unten nach oben aus. Zur Trennung der Listenelemente wird das Zeichen \\\\n als Eingabeelementtrennzeichen verwendet, sodass jedes Element in einer eigenen Zeile steht.\",\n    \"example2Title\": \"Bäume im Park zählen\",\n    \"example3Description\": \"In diesem Beispiel addieren wir neunzig verschiedene Werte – positive Zahlen, negative Zahlen, Ganzzahlen und Dezimalbrüche. Als Trennzeichen für die Eingabe setzen wir ein Komma. Nach der Addition aller Werte erhalten wir 0 als Ausgabe.\",\n    \"example3Title\": \"Summe der Ganzzahlen und Dezimalzahlen\",\n    \"example4Description\": \"In diesem Beispiel berechnen wir die Summe aller zehn Ziffern und aktivieren die Option „Laufende Summe drucken“. Die Zwischenwerte der Summe erhalten wir durch Addition. Die Ausgabe lautet daher: 0, 1 (0 + 1), 3 (0 + 1 + 2), 6 (0 + 1 + 2 + 3), 10 (0 + 1 + 2 + 3 + 4) usw.\",\n    \"example4Title\": \"Laufende Summe der Zahlen\",\n    \"extractionTypes\": {\n      \"delimiter\": {\n        \"description\": \"Passen Sie hier das Zahlentrennzeichen an. (Standardmäßig ein Zeilenumbruch.)\",\n        \"title\": \"Zahlentrennzeichen\"\n      },\n      \"smart\": {\n        \"description\": \"Automatische Erkennung von Zahlen in der Eingabe.\",\n        \"title\": \"Smart Sum\"\n      }\n    },\n    \"inputTitle\": \"Eingang\",\n    \"numberExtraction\": \"Zahlenextraktion\",\n    \"printRunningSum\": \"Laufende Summe drucken\",\n    \"printRunningSumDescription\": \"Zeigen Sie die Summe an, während sie Schritt für Schritt berechnet wird.\",\n    \"resultTitle\": \"Gesamt\",\n    \"runningSum\": \"Laufende Summe\",\n    \"shortDescription\": \"Summe der Zahlen berechnen\",\n    \"title\": \"Summe\",\n    \"toolInfo\": {\n      \"description\": \"Dies ist ein browserbasiertes Online-Dienstprogramm zum Berechnen der Summe mehrerer Zahlen. Sie können die Zahlen durch Komma, Leerzeichen oder ein beliebiges anderes Zeichen (einschließlich Zeilenumbruch) getrennt eingeben. Sie können auch einfach einen Textabschnitt mit numerischen Werten einfügen, die Sie summieren möchten. Das Dienstprogramm extrahiert diese und berechnet deren Summe.\",\n      \"title\": \"Was ist ein Zahlensummenrechner?\"\n    }\n  },\n  \"voltageDropInWire\": {\n    \"description\": \"Berechnet die Rundreisespannung und den Leistungsverlust in einem 2-adrigen Kabel\",\n    \"longDescription\": \"Dieser Rechner hilft bei der Ermittlung des Spannungsabfalls und der Verlustleistung in einem zweiadrigen Elektrokabel. Er berücksichtigt Kabellänge, Drahtquerschnitt, Materialwiderstand und Stromfluss. Das Tool berechnet den Hin- und Rückspannungsabfall, den Gesamtwiderstand des Kabels und die in Wärme umgewandelte Leistung. Dies ist besonders nützlich für Elektroingenieure, Elektriker und Bastler bei der Konstruktion elektrischer Systeme, um sicherzustellen, dass die Spannungspegel an der Last innerhalb akzeptabler Grenzen bleiben.\",\n    \"shortDescription\": \"Berechnen Sie Spannungsabfall und Leistungsverlust in elektrischen Kabeln basierend auf Länge, Material und Stromstärke\",\n    \"title\": \"Hin- und Rückspannungsabfall im Kabel\"\n  }\n}\n"
  },
  {
    "path": "public/locales/de/pdf.json",
    "content": "{\n  \"compressPdf\": {\n    \"compressedFileSize\": \"Komprimierte Dateigröße\",\n    \"compressingPdf\": \"PDF wird komprimiert...\",\n    \"compressionLevel\": \"Komprimierungsstufe\",\n    \"compressionSettings\": \"Komprimierungseinstellungen\",\n    \"description\": \"Reduzieren Sie die PDF-Dateigröße bei gleichbleibender Qualität mit Ghostscript\",\n    \"errorCompressingPdf\": \"PDF konnte nicht komprimiert werden: {{error}}\",\n    \"errorReadingPdf\": \"PDF-Datei konnte nicht gelesen werden. Bitte stellen Sie sicher, dass es sich um eine gültige PDF-Datei handelt.\",\n    \"fileSize\": \"Originaldateigröße\",\n    \"highCompression\": \"Hohe Kompression\",\n    \"highCompressionDescription\": \"Maximale Reduzierung der Dateigröße mit gewissem Qualitätsverlust\",\n    \"inputTitle\": \"Eingabe-PDF\",\n    \"longDescription\": \"Komprimieren Sie PDF-Dateien sicher in Ihrem Browser mit Ghostscript. Ihre Dateien verlassen nie Ihr Gerät. Das gewährleistet absolute Privatsphäre und reduziert gleichzeitig die Dateigröße für den E-Mail-Austausch, das Hochladen auf Websites oder zum Sparen von Speicherplatz. Unterstützt durch WebAssembly-Technologie.\",\n    \"lowCompression\": \"Geringe Kompression\",\n    \"lowCompressionDescription\": \"Reduzieren Sie die Dateigröße leicht mit minimalem Qualitätsverlust\",\n    \"mediumCompression\": \"Mittlere Kompression\",\n    \"mediumCompressionDescription\": \"Gleichgewicht zwischen Dateigröße und Qualität\",\n    \"pages\": \"Seitenanzahl\",\n    \"resultTitle\": \"Komprimiertes PDF\",\n    \"shortDescription\": \"Komprimieren Sie PDF-Dateien sicher in Ihrem Browser\",\n    \"title\": \"PDF komprimieren\"\n  },\n  \"editor\": {\n    \"description\": \"Erweiterter PDF-Editor mit Funktionen zum Kommentieren, Ausfüllen von Formularen, Hervorheben und Exportieren. Bearbeiten Sie Ihre PDFs direkt im Browser mit professionellen Tools wie Texteinfügung, Zeichnen, Hervorheben, Unterschreiben und Ausfüllen von Formularen.\",\n    \"shortDescription\": \"Bearbeiten Sie PDFs mit erweiterten Werkzeugen zum Kommentieren, Signieren und Bearbeiten\",\n    \"title\": \"PDF Editor\"\n  },\n  \"merge\": {\n    \"inputTitle\": \"Eingabe-PDF\",\n    \"loadingText\": \"Seiten extrahieren\",\n    \"resultTitle\": \"Zusammengeführtes PDF ausgeben\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie mehrere PDF-Dateien zu einem einzigen Dokument zusammenführen. Laden Sie dazu einfach die zusammenzuführenden PDF-Dateien hoch. Das Tool fügt dann alle Seiten der Eingabedateien zu einem einzigen PDF-Dokument zusammen.\",\n      \"title\": \"Wie verwende ich das Tool zum Zusammenführen von PDFs?\"\n    }\n  },\n  \"mergePdf\": {\n    \"description\": \"Kombinieren Sie mehrere PDF-Dateien zu einem einzigen Dokument.\",\n    \"inputTitle\": \"Eingabe-PDFs\",\n    \"mergingPdfs\": \"PDFs zusammenführen\",\n    \"pdfOptions\": \"PDF-Optionen\",\n    \"resultTitle\": \"Zusammengeführte PDF\",\n    \"shortDescription\": \"Mehrere PDF-Dateien zu einem einzigen Dokument zusammenführen\",\n    \"sortByFileName\": \"Nach Dateinamen sortieren\",\n    \"sortByFileNameDescription\": \"PDFs alphabetisch nach Dateinamen sortieren\",\n    \"sortByUploadOrder\": \"Nach Upload-Reihenfolge sortieren\",\n    \"sortByUploadOrderDescription\": \"Behalten Sie PDFs in der Reihenfolge bei, in der sie hochgeladen wurden\",\n    \"title\": \"PDF zusammenführen\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie mehrere PDF-Dateien zu einem einzigen Dokument zusammenführen. Sie können die Sortierung der PDFs wählen, und das Tool fügt sie in der angegebenen Reihenfolge zusammen.\",\n      \"title\": \"PDF-Dateien zusammenführen\"\n    }\n  },\n  \"pdfToEpub\": {\n    \"description\": \"Wandeln Sie PDF-Dokumente in EPUB-Dateien um, um eine bessere E-Reader-Kompatibilität zu erzielen.\",\n    \"shortDescription\": \"Konvertieren Sie PDF-Dateien in das EPUB-Format\",\n    \"title\": \"PDF zu EPUB\"\n  },\n  \"pdfToPng\": {\n    \"description\": \"Wandeln Sie PDF-Dokumente in PNG-Panels um.\",\n    \"longDescription\": \"Laden Sie eine PDF-Datei hoch und konvertieren Sie jede Seite direkt in Ihrem Browser in ein hochwertiges PNG-Bild. Dieses Tool eignet sich ideal zum Extrahieren visueller Inhalte oder zum Teilen einzelner Seiten. Es werden keine Daten hochgeladen – alles läuft lokal.\",\n    \"shortDescription\": \"Konvertieren Sie PDF in PNG-Bilder\",\n    \"title\": \"PDF zu PNG\"\n  },\n  \"convertToPdf\": {\n    \"title\": \"Bilder in PDF konvertieren\",\n    \"description\": \"Verschiedene Bildformate (PNG, GIF, JPG, TIF, PSD, SVG, WEBP, HEIC, RAW) in PDF konvertieren, mit Optionen zum Skalieren des Bildes und zur Wahl der Seitenorientierung.\",\n    \"shortDescription\": \"Bilder in PDF mit Skalierungs- und Orientierungskontrolle konvertieren\"\n  },\n  \"protectPdf\": {\n    \"description\": \"Fügen Sie Ihren PDF-Dateien sicher in Ihrem Browser einen Passwortschutz hinzu\",\n    \"shortDescription\": \"PDF-Dateien sicher mit einem Passwort schützen\",\n    \"title\": \"PDF schützen\"\n  },\n  \"rotatePdf\": {\n    \"allPagesWillBeRotated\": \"Alle {{count}} Seiten werden gedreht\",\n    \"angleOptions\": {\n      \"clockwise90\": \"90° im Uhrzeigersinn\",\n      \"counterClockwise270\": \"270° (90° gegen den Uhrzeigersinn)\",\n      \"upsideDown180\": \"180° (auf den Kopf gestellt)\"\n    },\n    \"applyToAllPages\": \"Auf alle Seiten anwenden\",\n    \"description\": \"Drehen Sie Seiten in einem PDF-Dokument.\",\n    \"inputTitle\": \"Eingabe-PDF\",\n    \"longDescription\": \"Ändern Sie die Ausrichtung von PDF-Seiten, indem Sie sie um 90, 180 oder 270 Grad drehen. Nützlich zum Korrigieren falsch gescannter Dokumente oder zum Vorbereiten von PDFs für den Druck.\",\n    \"pageRangesDescription\": \"Geben Sie Seitenzahlen oder Bereiche durch Kommas getrennt ein (z. B. 1,3,5-7).\",\n    \"pageRangesPlaceholder\": \"z.B. 1,5-8\",\n    \"pagesWillBeRotated\": \"{{count}} Seite{{count !== 1 ? 's' : ''}} wird gedreht\",\n    \"pdfPageCount\": \"PDF hat {{count}} Seite{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"Gedrehtes PDF\",\n    \"rotatingPages\": \"Seiten drehen\",\n    \"rotationAngle\": \"Drehwinkel\",\n    \"rotationSettings\": \"Rotationseinstellungen\",\n    \"shortDescription\": \"Seiten in einem PDF-Dokument drehen\",\n    \"title\": \"PDF drehen\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie Seiten in einem PDF-Dokument drehen. Sie können alle Seiten drehen oder einzelne Seiten auswählen. Wählen Sie einen Drehwinkel: 90° im Uhrzeigersinn, 180° (auf dem Kopf) oder 270° (90° gegen den Uhrzeigersinn). Um bestimmte Seiten zu drehen, deaktivieren Sie „Auf alle Seiten anwenden“ und geben Sie Seitenzahlen oder -bereiche durch Kommas getrennt ein (z. B. 1, 3, 5-7).\",\n      \"title\": \"So verwenden Sie das Tool zum Drehen von PDFs\"\n    }\n  },\n  \"splitPdf\": {\n    \"description\": \"Extrahieren Sie bestimmte Seiten aus einem PDF-Dokument.\",\n    \"extractingPages\": \"Seiten extrahieren\",\n    \"inputTitle\": \"Eingabe-PDF\",\n    \"pageExtractionPreview\": \"{{count}} Seite{{count !== 1 ? 's' : ''}} wird extrahiert\",\n    \"pageRangesDescription\": \"Geben Sie Seitenzahlen oder Bereiche durch Kommas getrennt ein (z. B. 1,3,5-7).\",\n    \"pageRangesPlaceholder\": \"z.B. 1,5-8\",\n    \"pageSelection\": \"Seitenauswahl\",\n    \"pdfPageCount\": \"PDF hat {{count}} Seite{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"Extrahiertes PDF\",\n    \"shortDescription\": \"Extrahieren Sie bestimmte Seiten aus einer PDF-Datei\",\n    \"title\": \"PDF teilen\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie bestimmte Seiten aus einem PDF-Dokument extrahieren. Sie können einzelne Seiten oder Seitenbereiche zum Extrahieren angeben.\",\n      \"title\": \"PDF teilen\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/de/string.json",
    "content": "{\n  \"base64\": {\n    \"decode\": \"Base64-Dekodierung\",\n    \"description\": \"Kodieren oder dekodieren Sie Text mit der Base64-Kodierung.\",\n    \"encode\": \"Base64-Kodierung\",\n    \"inputTitle\": \"Eingabedaten\",\n    \"optionsTitle\": \"Base64-Optionen\",\n    \"resultTitle\": \"Ergebnis\",\n    \"shortDescription\": \"Kodieren oder dekodieren Sie Daten mit Base64.\",\n    \"title\": \"Base64-Encoder/Decoder\",\n    \"toolInfo\": {\n      \"description\": \"Base64 ist ein Kodierungsschema, das Daten im ASCII-String-Format darstellt, indem es sie in eine Radix-64-Darstellung übersetzt. Obwohl es zur Kodierung von Strings verwendet werden kann, wird es üblicherweise zur Kodierung binärer Daten für die Übertragung über Medien verwendet, die für die Verarbeitung von Textdaten ausgelegt sind.\",\n      \"title\": \"Was ist Base64?\"\n    }\n  },\n  \"censor\": {\n    \"description\": \"Dienstprogramm zum Zensieren von Wörtern in Texten. Laden Sie Ihren Text in das Eingabeformular links, geben Sie alle Schimpfwörter in den Optionen an, und Sie erhalten sofort zensierten Text im Ausgabebereich., longDescription: 'Mit diesem Online-Tool können Sie bestimmte Wörter in jedem Text zensieren. Sie können eine Liste unerwünschter Wörter (z. B. Schimpfwörter oder Geheimwörter) angeben, die das Programm durch alternative Wörter ersetzt und einen lesbaren Text erstellt. Die Wörter können in einem mehrzeiligen Textfeld in den Optionen angegeben werden, indem Sie pro Zeile ein Wort eingeben.', keywords: ['text', 'zensieren', 'wörter', 'zeichen'], component: lazy(() => import('./index')), i18n: { name: 'string:censor.title', description: 'string:censor.description\",\n    \"shortDescription\": \"Maskieren Sie Schimpfwörter schnell oder ersetzen Sie sie durch alternative Wörter.\",\n    \"title\": \"Textzensur\"\n  },\n  \"createPalindrome\": {\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Erstellen von Palindromen aus beliebigem Text. Geben Sie Text ein und verwandeln Sie ihn sofort in ein Palindrom, das vorwärts und rückwärts gleich gelesen wird. Ideal für Wortspiele, das Erstellen symmetrischer Textmuster oder das Erkunden sprachlicher Kuriositäten.\",\n    \"shortDescription\": \"Erstellen Sie Text, der vorwärts und rückwärts gleich gelesen wird\",\n    \"title\": \"Palindrom erstellen\"\n  },\n  \"extractSubstring\": {\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Extrahieren von Teilzeichenfolgen aus Text. Geben Sie Ihren Text ein und legen Sie Start- und Endposition fest, um den gewünschten Teil zu extrahieren. Ideal für die Datenverarbeitung, Textanalyse oder das Extrahieren spezifischer Inhalte aus größeren Textblöcken.\",\n    \"shortDescription\": \"Extrahieren Sie einen Textabschnitt zwischen angegebenen Positionen\",\n    \"title\": \"Teilzeichenfolge extrahieren\"\n  },\n  \"hiddenCharacterDetector\": {\n    \"analysisOptions\": \"Analyseoptionen\",\n    \"category\": \"Kategorie\",\n    \"description\": \"Erkennen Sie versteckte Unicode-Zeichen, insbesondere RTL-Override-Zeichen, die für Angriffe verwendet werden könnten.\",\n    \"foundChars\": \"Gefunden {{count}} versteckte(s) Zeichen:\",\n    \"inputPlaceholder\": \"Geben Sie Text ein, um nach versteckten Zeichen zu suchen ...\",\n    \"inputTitle\": \"Zu analysierender Text\",\n    \"invisibleChar\": \"Unsichtbarer Charakter\",\n    \"invisibleFound\": \"Unsichtbare Zeichen gefunden\",\n    \"longDescription\": \"Mit diesem Tool können Sie versteckte Unicode-Zeichen in Texten erkennen, insbesondere Rechts-nach-Links-Override-Zeichen (RTL), die für Angriffe missbraucht werden können. Es erkennt unsichtbare Zeichen, Zeichen mit der Breite Null und andere potenziell schädliche Unicode-Sequenzen, die in scheinbar harmlosem Text versteckt sein können.\",\n    \"noHiddenChars\": \"Im Text wurden keine versteckten Zeichen erkannt.\",\n    \"optionsDescription\": \"Konfigurieren Sie, welche Arten von versteckten Zeichen erkannt werden sollen und wie die Ergebnisse angezeigt werden sollen.\",\n    \"position\": \"Position\",\n    \"rtlAlert\": \"⚠️ RTL-Override-Zeichen erkannt! Dieser Text kann schädliche versteckte Zeichen enthalten.\",\n    \"rtlFound\": \"RTL-Override gefunden\",\n    \"rtlOverride\": \"RTL-Überschreibungszeichen\",\n    \"rtlWarning\": \"WARNUNG: RTL-Override-Zeichen erkannt! Dies könnte für Angriffe missbraucht werden.\",\n    \"shortDescription\": \"Suchen Sie nach versteckten Unicode-Zeichen im Text\",\n    \"summary\": \"Zusammenfassung der Analyse\",\n    \"title\": \"Detektor für versteckte Zeichen\",\n    \"totalChars\": \"Gesamtzahl der versteckten Zeichen: {{count}}\",\n    \"unicode\": \"Unicode\",\n    \"zeroWidthChar\": \"Zeichen mit Nullbreite\",\n    \"zeroWidthFound\": \"Zeichen mit der Breite Null gefunden\"\n  },\n  \"join\": {\n    \"blankLinesAndTrailingSpaces\": \"Leere Zeilen und Leerzeichen am Ende\",\n    \"deleteBlankDescription\": \"Löschen Sie Zeilen, die keine Textsymbole enthalten.\",\n    \"deleteBlankTitle\": \"Leere Zeilen löschen\",\n    \"deleteTrailingDescription\": \"Entfernen Sie Leerzeichen und Tabulatoren am Zeilenende.\",\n    \"deleteTrailingTitle\": \"Löschen Sie abschließende Leerzeichen\",\n    \"description\": \"Fügen Sie Textteile mit anpassbaren Trennzeichen zusammen.\",\n    \"inputTitle\": \"Textstücke\",\n    \"joinCharacterDescription\": \"Symbol, das unterbrochene Textteile verbindet. (Standardmäßig Leerzeichen.)\",\n    \"joinCharacterPlaceholder\": \"Charakter beitreten\",\n    \"resultTitle\": \"Verbundener Text\",\n    \"shortDescription\": \"Verbinden Sie Textelemente mit einem festgelegten Trennzeichen\",\n    \"textMergedOptions\": \"Optionen für zusammengeführten Text\",\n    \"title\": \"Text verbinden\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie Textteile zusammenfügen. Es nimmt eine Liste von Textwerten, getrennt durch Zeilenumbrüche, und fügt sie zusammen. Sie können das Zeichen festlegen, das zwischen den Teilen des zusammengefügten Textes platziert wird. Außerdem können Sie alle Leerzeilen ignorieren und Leerzeichen und Tabulatoren am Zeilenende entfernen. Textabulous!\",\n      \"title\": \"Was ist ein Textverbinder?\"\n    }\n  },\n  \"palindrome\": {\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zur Überprüfung von Palindromen. Überprüfen Sie sofort, ob sich Ihr Text vorwärts und rückwärts gleich liest. Ideal für Worträtsel, linguistische Analysen oder die Validierung symmetrischer Textmuster. Unterstützt verschiedene Trennzeichen und die Erkennung mehrteiliger Palindrome.\",\n    \"shortDescription\": \"Überprüfen Sie, ob der Text vorwärts und rückwärts gleich gelesen wird\",\n    \"title\": \"Palindrom\"\n  },\n  \"passwordGenerator\": {\n    \"avoidAmbiguous\": \"Vermeiden Sie mehrdeutige Zeichen (i, I, l, 0, O).\",\n    \"description\": \"Generieren Sie sichere, zufällige Passwörter mit anpassbarer Länge und Zeichenart. Wählen Sie zwischen Kleinbuchstaben, Großbuchstaben, Zahlen und Sonderzeichen. Optional können Sie mehrdeutige Zeichen vermeiden, um die Lesbarkeit zu verbessern.\",\n    \"includeLowercase\": \"Kleinbuchstaben einschließen (a-z)\",\n    \"includeNumbers\": \"Zahlen einschließen (0-9)\",\n    \"includeSymbols\": \"Sonderzeichen einschließen\",\n    \"includeUppercase\": \"Großbuchstaben einschließen (A-Z)\",\n    \"lengthDesc\": \"Länge des Passwortes\",\n    \"lengthPlaceholder\": \"z.B. 12\",\n    \"optionsTitle\": \"Passwortoptionen\",\n    \"resultTitle\": \"Generiertes Passwort\",\n    \"shortDescription\": \"Generieren Sie sichere, zufällige Passwörter mit benutzerdefinierten Optionen\",\n    \"title\": \"Passwortgenerator\",\n    \"toolInfo\": {\n      \"description\": \"Dieses Tool generiert sichere, zufällige Passwörter basierend auf Ihren ausgewählten Kriterien. Sie können die Länge anpassen, verschiedene Zeichentypen ein- oder ausschließen und mehrdeutige Zeichen für eine bessere Lesbarkeit vermeiden. Ideal für die Erstellung sicherer Passwörter für Konten, Anwendungen oder andere Sicherheitsanforderungen.\",\n      \"title\": \"Über den Passwortgenerator\"\n    }\n  },\n  \"quote\": {\n    \"allowDoubleQuotation\": \"Doppelte Anführungszeichen zulassen\",\n    \"description\": \"Fügen Sie mit anpassbaren Optionen Anführungszeichen um den Text hinzu.\",\n    \"inputTitle\": \"Eingabetext\",\n    \"leftQuoteDescription\": \"Anführungszeichen links\",\n    \"processAsMultiLine\": \"Als mehrzeiligen Text verarbeiten\",\n    \"quoteEmptyLines\": \"Leere Zeilen in Zitieren setzen\",\n    \"quoteOptions\": \"Angebotsoptionen\",\n    \"resultTitle\": \"Zitierter Text\",\n    \"rightQuoteDescription\": \"Anführungszeichen rechts\",\n    \"shortDescription\": \"Fügen Sie Anführungszeichen um Text mit verschiedenen Stilen hinzu\",\n    \"title\": \"Textzitierer\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie Text in Anführungszeichen setzen. Sie können verschiedene Anführungszeichen wählen, mehrzeiligen Text bearbeiten und die Verarbeitung leerer Zeilen steuern. Es ist nützlich, um Text für die Programmierung vorzubereiten, Daten zu formatieren oder stilisierten Text zu erstellen.\",\n      \"title\": \"Textzitierer\"\n    }\n  },\n  \"randomizeCase\": {\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zur zufälligen Groß- und Kleinschreibung. Geben Sie Ihren Text ein und transformieren Sie ihn sofort mit zufälligen Groß- und Kleinbuchstaben. Perfekt für einzigartige Texteffekte, zum Testen der Groß- und Kleinschreibung oder zum Generieren abwechslungsreicher Textmuster.\",\n    \"shortDescription\": \"Groß- und Kleinschreibung von Buchstaben im Text zufällig anordnen\",\n    \"title\": \"Groß-/Kleinschreibung randomisieren\"\n  },\n  \"removeDuplicateLines\": {\n    \"description\": \"Laden Sie Ihren Text in das Eingabeformular links und Sie erhalten sofort Text ohne doppelte Zeilen im Ausgabebereich. Leistungsstark, kostenlos und schnell. Textzeilen laden – einzigartige Textzeilen erhalten\",\n    \"shortDescription\": \"Löschen Sie schnell alle wiederholten Zeilen aus dem Text\",\n    \"title\": \"Entfernen Sie doppelte Zeilen\"\n  },\n  \"repeat\": {\n    \"delimiterDescription\": \"Trennzeichen für Ausgabekopien.\",\n    \"delimiterPlaceholder\": \"Trennzeichen\",\n    \"description\": \"Wiederholen Sie Text mehrmals mit anpassbaren Trennzeichen.\",\n    \"inputTitle\": \"Eingabetext\",\n    \"numberPlaceholder\": \"Nummer\",\n    \"repeatAmountDescription\": \"Anzahl der Wiederholungen.\",\n    \"repetitionsDelimiter\": \"Wiederholungsbegrenzer\",\n    \"resultTitle\": \"Wiederholter Text\",\n    \"shortDescription\": \"Text mehrmals wiederholen\",\n    \"textRepetitions\": \"Textwiederholungen\",\n    \"title\": \"Text wiederholen\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie einen bestimmten Text mit einem optionalen Trennzeichen mehrmals wiederholen.\",\n      \"title\": \"Text wiederholen\"\n    }\n  },\n  \"reverse\": {\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Umkehren von Text. Geben Sie beliebigen Text ein und lassen Sie ihn sofort Zeichen für Zeichen umkehren. Ideal zum Erstellen von Spiegeltext, Analysieren von Palindromen oder zum Experimentieren mit Textmustern. Leerzeichen und Sonderzeichen bleiben beim Umkehren erhalten.\",\n    \"inputTitle\": \"Umzukehrender Text\",\n    \"processMultiLine\": \"Mehrzeiligen Text verarbeiten\",\n    \"processMultiLineDescription\": \"Jede Zeile wird unabhängig umgekehrt\",\n    \"resultTitle\": \"Umgekehrter Text\",\n    \"reversalOptions\": \"Umkehroptionen\",\n    \"shortDescription\": \"Kehren Sie jeden Text Zeichen für Zeichen um\",\n    \"skipEmptyLines\": \"Leere Zeilen überspringen\",\n    \"skipEmptyLinesDescription\": \"Leere Zeilen werden aus der Ausgabe entfernt\",\n    \"title\": \"Umkehren\",\n    \"trimWhitespace\": \"Leerzeichen entfernen\",\n    \"trimWhitespaceDescription\": \"Entfernen Sie führende und nachfolgende Leerzeichen aus jeder Zeile\"\n  },\n  \"rot13\": {\n    \"description\": \"Kodieren oder dekodieren Sie Text mit der ROT13-Chiffre.\",\n    \"inputTitle\": \"Eingabetext\",\n    \"resultTitle\": \"ROT13-Ergebnis\",\n    \"shortDescription\": \"Kodieren oder dekodieren Sie Text mit der ROT13-Chiffre.\",\n    \"title\": \"ROT13 Encoder/Decoder\",\n    \"toolInfo\": {\n      \"description\": \"ROT13 (Rotate by 13 places) ist eine einfache Buchstabenersetzungs-Chiffre, die einen Buchstaben durch den 13. Buchstaben im Alphabet ersetzt. ROT13 ist ein Sonderfall der Caesar-Chiffre, die im antiken Rom entwickelt wurde. Da das englische Alphabet 26 Buchstaben umfasst, ist ROT13 seine eigene Umkehrung. Das heißt, um ROT13 rückgängig zu machen, wird derselbe Algorithmus angewendet, sodass dieselbe Aktion zum Verschlüsseln und Entschlüsseln verwendet werden kann.\",\n      \"title\": \"Was ist ROT13?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"Drehen Sie Zeichen im Text um angegebene Positionen.\",\n    \"inputTitle\": \"Eingabetext\",\n    \"processAsMultiLine\": \"Als mehrzeiligen Text verarbeiten (jede Zeile einzeln drehen)\",\n    \"resultTitle\": \"Gedrehter Text\",\n    \"rotateLeft\": \"Nach links drehen\",\n    \"rotateRight\": \"Nach rechts drehen\",\n    \"rotationOptions\": \"Rotationsoptionen\",\n    \"shortDescription\": \"Verschieben Sie Zeichen im Text um die Position.\",\n    \"stepDescription\": \"Anzahl der zu drehenden Positionen\",\n    \"title\": \"Text drehen\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Werkzeug können Sie Zeichen in einer Zeichenfolge um eine bestimmte Anzahl von Positionen drehen. Sie können nach links oder rechts drehen und mehrzeiligen Text verarbeiten, indem Sie jede Zeile einzeln drehen. Die Zeichenfolgenrotation ist nützlich für einfache Texttransformationen, die Erstellung von Mustern oder die Implementierung grundlegender Verschlüsselungstechniken.\",\n      \"title\": \"Saitenrotation\"\n    }\n  },\n  \"split\": {\n    \"charAfterChunkDescription\": \"Zeichen nach jedem Block\",\n    \"charBeforeChunkDescription\": \"Zeichen vor jedem Block\",\n    \"chunksDescription\": \"Anzahl der gleich langen Chunks in der Ausgabe.\",\n    \"chunksTitle\": \"Verwenden Sie eine Anzahl von Chunks\",\n    \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Teilen von Text. Geben Sie Ihren Text ein und wählen Sie ein Trennzeichen, um ihn in mehrere Teile aufzuteilen. Ideal für die Datenverarbeitung, Textbearbeitung oder das Extrahieren bestimmter Inhalte aus größeren Textblöcken.\",\n    \"lengthDescription\": \"Anzahl der Symbole, die in jeden Ausgabeblock eingefügt werden.\",\n    \"lengthTitle\": \"Länge zum Teilen verwenden\",\n    \"outputSeparatorDescription\": \"Zeichen, das zwischen die geteilten Blöcke eingefügt wird.\\n(Standardmäßig ist es das Zeilenumbruchzeichen „\\\\n“.)\",\n    \"outputSeparatorOptions\": \"Ausgabetrennzeichenoptionen\",\n    \"regexDescription\": \"Regulärer Ausdruck, der zum Aufteilen von Text verwendet wird.\\n(Standardmäßig mehrere Leerzeichen.)\",\n    \"regexTitle\": \"Verwenden Sie einen regulären Ausdruck zum Aufteilen\",\n    \"resultTitle\": \"Textstücke\",\n    \"shortDescription\": \"Text mithilfe eines Trennzeichens in mehrere Teile aufteilen\",\n    \"splitSeparatorOptions\": \"Optionen für geteilte Trennzeichen\",\n    \"symbolDescription\": \"Zeichen, das zum Aufteilen des Textes verwendet wird.\\n(Standardmäßig Leerzeichen.)\",\n    \"symbolTitle\": \"Verwenden Sie ein Symbol zum Teilen\",\n    \"title\": \"Teilt\"\n  },\n  \"statistic\": {\n    \"characterFrequencyAnalysis\": \"Zeichenhäufigkeitsanalyse\",\n    \"characterFrequencyAnalysisDescription\": \"Zählen Sie, wie oft jedes Zeichen im Text vorkommt\",\n    \"delimitersOptions\": \"Trennzeichenoptionen\",\n    \"description\": \"Analysieren Sie Texte und erstellen Sie umfassende Statistiken.\",\n    \"includeEmptyLines\": \"Leere Zeilen einschließen\",\n    \"includeEmptyLinesDescription\": \"Berücksichtigen Sie beim Zählen von Zeilen auch Leerzeilen\",\n    \"inputTitle\": \"Eingabetext\",\n    \"resultTitle\": \"Textstatistik\",\n    \"sentenceDelimitersDescription\": \"Geben Sie benutzerdefinierte Zeichen ein, die zur Abgrenzung von Sätzen in Ihrer Sprache verwendet werden (durch Komma getrennt), oder lassen Sie das Feld als Standard leer.\",\n    \"sentenceDelimitersPlaceholder\": \"z. B. ., !, ?, ...\",\n    \"shortDescription\": \"Erhalten Sie Statistiken zu Ihrem Text\",\n    \"statisticsOptions\": \"Statistikoptionen\",\n    \"title\": \"Textstatistik\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie Text analysieren und umfassende Statistiken erstellen, darunter Zeichenanzahl, Wortanzahl, Zeilenanzahl und Häufigkeitsanalyse von Zeichen und Wörtern.\",\n      \"title\": \"Was ist ein {{title}}?\"\n    },\n    \"wordDelimitersDescription\": \"Geben Sie einen benutzerdefinierten regulären Ausdruck ein, um Wörter zu zählen, oder lassen Sie das Feld für die Standardeinstellung leer.\",\n    \"wordDelimitersPlaceholder\": \"zB. \\\\s.,;:!?\\\"«»()…\",\n    \"wordFrequencyAnalysis\": \"Worthäufigkeitsanalyse\",\n    \"wordFrequencyAnalysisDescription\": \"Zählen Sie, wie oft jedes Wort im Text vorkommt\"\n  },\n  \"textReplacer\": {\n    \"description\": \"Ersetzen Sie Textmuster durch neue Inhalte.\",\n    \"findPatternInText\": \"Dieses Muster im Text finden\",\n    \"findPatternUsingRegexp\": \"Suchen eines Musters mithilfe eines regulären Ausdrucks\",\n    \"inputTitle\": \"Zu ersetzender Text\",\n    \"newTextPlaceholder\": \"Neuer Text\",\n    \"regexpDescription\": \"Geben Sie den regulären Ausdruck ein, den Sie ersetzen möchten.\",\n    \"replacePatternDescription\": \"Geben Sie das Muster ein, das zum Ersetzen verwendet werden soll.\",\n    \"replaceText\": \"Text ersetzen\",\n    \"resultTitle\": \"Text mit Ersetzungen\",\n    \"searchPatternDescription\": \"Geben Sie das Textmuster ein, das Sie ersetzen möchten.\",\n    \"searchText\": \"Suchtext\",\n    \"shortDescription\": \"Ersetzen Sie schnell Text in Ihrem Inhalt\",\n    \"title\": \"Textersetzung\",\n    \"toolInfo\": {\n      \"description\": \"Ersetzen Sie mit diesem einfachen, browserbasierten Tool ganz einfach Text in Ihren Inhalten. Geben Sie einfach Ihren Text ein, legen Sie den zu ersetzenden Text und den Ersetzungswert fest und erhalten Sie sofort die aktualisierte Version.\",\n      \"title\": \"Textersetzung\"\n    }\n  },\n  \"toMorse\": {\n    \"dashSymbolDescription\": \"Symbol, das dem Bindestrich im Morsecode entspricht.\",\n    \"description\": \"Konvertieren Sie Text in Morsecode.\",\n    \"dotSymbolDescription\": \"Symbol, das dem Punkt im Morsecode entspricht.\",\n    \"longSignal\": \"Langes Signal\",\n    \"resultTitle\": \"Morsezeichen\",\n    \"shortDescription\": \"Text schnell in Morse kodieren\",\n    \"shortSignal\": \"Kurzes Signal\",\n    \"title\": \"Zeichenfolge zu Morse\"\n  },\n  \"truncate\": {\n    \"addTruncationIndicator\": \"Trunkierungsindikator hinzufügen\",\n    \"charactersPlaceholder\": \"Charaktere\",\n    \"description\": \"Kürzen Sie den Text auf eine bestimmte Länge.\",\n    \"indicatorDescription\": \"Zeichen, die am Ende (oder Anfang) des Textes hinzugefügt werden sollen. Hinweis: Sie werden zur Länge gezählt.\",\n    \"inputTitle\": \"Eingabetext\",\n    \"leftSideDescription\": \"Entfernen Sie Zeichen vom Anfang des Textes.\",\n    \"leftSideTruncation\": \"Linksseitige Kürzung\",\n    \"lengthAndLines\": \"Länge und Linien\",\n    \"lineByLineDescription\": \"Kürzen Sie jede Zeile einzeln.\",\n    \"lineByLineTruncating\": \"Zeilenweises Abschneiden\",\n    \"maxLengthDescription\": \"Anzahl der Zeichen, die im Text verbleiben sollen.\",\n    \"numberPlaceholder\": \"Nummer\",\n    \"resultTitle\": \"Abgeschnittener Text\",\n    \"rightSideDescription\": \"Entfernen Sie Zeichen vom Ende des Textes.\",\n    \"rightSideTruncation\": \"Rechtsseitige Kürzung\",\n    \"shortDescription\": \"Text auf eine bestimmte Länge kürzen\",\n    \"suffixAndAffix\": \"Suffix und Affix\",\n    \"title\": \"Text abschneiden\",\n    \"toolInfo\": {\n      \"description\": \"Laden Sie Ihren Text in das Eingabeformular links und Sie erhalten rechts automatisch gekürzten Text.\",\n      \"title\": \"Text kürzen\"\n    },\n    \"truncationSide\": \"Abschneideseite\"\n  },\n  \"uppercase\": {\n    \"description\": \"Wandeln Sie Text in Großbuchstaben um.\",\n    \"inputTitle\": \"Eingabetext\",\n    \"resultTitle\": \"Großbuchstaben\",\n    \"shortDescription\": \"Text in Großbuchstaben umwandeln\",\n    \"title\": \"In Großbuchstaben umwandeln\"\n  },\n  \"urlDecode\": {\n    \"inputTitle\": \"Eingabezeichenfolge (URL-Escapezeichen)\",\n    \"resultTitle\": \"Ausgabezeichenfolge\",\n    \"toolInfo\": {\n      \"description\": \"Laden Sie Ihre Zeichenfolge und die URL wird automatisch entmaskiert.\",\n      \"longDescription\": \"Dieses Tool dekodiert eine zuvor URL-kodierte Zeichenfolge. URL-Dekodierung ist die Umkehrung der URL-Kodierung. Alle prozentkodierten Zeichen werden in verständliche Zeichen dekodiert. Zu den bekanntesten prozentkodierten Werten zählen %20 für ein Leerzeichen, %3a für einen Doppelpunkt, %2f für einen Schrägstrich und %3f für ein Fragezeichen. Die beiden Ziffern nach dem Prozentzeichen geben die hexadezimalen Zeichencodewerte des Zeichens an.\",\n      \"shortDescription\": \"Entfernen Sie schnell die URL aus einer Zeichenfolge.\",\n      \"title\": \"String-URL-Decoder\"\n    }\n  },\n  \"urlEncode\": {\n    \"encodingOption\": {\n      \"nonSpecialCharDescription\": \"Wenn diese Option ausgewählt ist, werden alle Zeichen in der Eingabezeichenfolge in die URL-Kodierung konvertiert (nicht nur spezielle).\",\n      \"nonSpecialCharPlaceholder\": \"Nicht-Sonderzeichen kodieren\",\n      \"title\": \"Kodierungsoptionen\"\n    },\n    \"inputTitle\": \"Eingabezeichenfolge\",\n    \"resultTitle\": \"URL-Escape-String\",\n    \"toolInfo\": {\n      \"description\": \"Laden Sie Ihre Zeichenfolge und sie wird automatisch mit URL-Escapezeichen versehen.\",\n      \"longDescription\": \"Dieses Tool kodiert eine Zeichenfolge URL-kodiert. Spezielle URL-Zeichen werden in Prozentzeichen umgewandelt. Diese Kodierung wird als Prozentkodierung bezeichnet, da der numerische Wert jedes Zeichens in ein Prozentzeichen gefolgt von einem zweistelligen Hexadezimalwert umgewandelt wird. Die Hexadezimalwerte werden anhand des Codepunktwerts des Zeichens bestimmt. Beispielsweise wird ein Leerzeichen in %20, ein Doppelpunkt in %3a und ein Schrägstrich in %2f umgewandelt. Nicht spezielle Zeichen bleiben unverändert. Falls Sie auch nicht spezielle Zeichen in Prozentkodierung umwandeln müssen, haben wir eine zusätzliche Option hinzugefügt, die dies ermöglicht. Aktivieren Sie dazu die Option „encode-non-special-chars“.\",\n      \"shortDescription\": \"Schnelles URL-Escapen einer Zeichenfolge.\",\n      \"title\": \"String-URL-Encoder\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/de/time.json",
    "content": "{\n  \"checkLeapYears\": {\n    \"description\": \"Prüfen Sie, ob ein Jahr ein Schaltjahr ist, und erhalten Sie Informationen zum Schaltjahr.\",\n    \"exampleDescription\": \"Eine unserer Freundinnen wurde in einem Schaltjahr am 29. Februar geboren und hat daher nur alle vier Jahre Geburtstag. Da wir uns nie genau merken können, wann ihr Geburtstag ist, erstellen wir mit unserem Programm eine Erinnerungsliste für die kommenden Schaltjahre. Dazu laden wir eine Jahresfolge von 2025 bis 2040 in die Eingabe und ermitteln den Status jedes Jahres. Wenn das Programm anzeigt, dass es ein Schaltjahr ist, wissen wir, dass wir am 29. Februar zu einer Geburtstagsfeier eingeladen werden.\",\n    \"exampleTitle\": \"Finden Sie Geburtstage am 29. Februar\",\n    \"inputTitle\": \"Jahr eingeben\",\n    \"resultTitle\": \"Schaltjahrergebnis\",\n    \"shortDescription\": \"Prüfen, ob ein Jahr ein Schaltjahr ist\",\n    \"title\": \"Schaltjahre prüfen\",\n    \"toolInfo\": {\n      \"description\": \"Ein Schaltjahr ist ein Jahr mit einem zusätzlichen Tag (29. Februar), um das Kalenderjahr mit dem astronomischen Jahr zu synchronisieren. Schaltjahre gibt es alle vier Jahre, mit Ausnahme der Jahre, die durch 100, aber nicht durch 400 teilbar sind.\",\n      \"title\": \"Was ist ein Schaltjahr?\"\n    }\n  },\n  \"convertDaysToHours\": {\n    \"addHoursName\": \"Stundennamen hinzufügen\",\n    \"addHoursNameDescription\": \"Hängen Sie die Zeichenfolge „Stunden“ an die Ausgabewerte an\",\n    \"description\": \"Wandeln Sie Tage mit anpassbaren Optionen in Stunden um.\",\n    \"hoursName\": \"Stunden Name\",\n    \"shortDescription\": \"Tage in Stunden umrechnen\",\n    \"title\": \"Tage in Stunden umrechnen\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie Tage in Stunden umrechnen. Sie können Tage als Zahlen oder mit Einheiten eingeben, und das Tool rechnet sie in Stunden um. Sie können den Ausgabewerten auch das Suffix „Stunden“ anhängen.\",\n      \"title\": \"Tage in Stunden umrechnen\"\n    }\n  },\n  \"convertHoursToDays\": {\n    \"addDaysName\": \"Tagesnamen hinzufügen\",\n    \"addDaysNameDescription\": \"Hängen Sie die Zeichenfolge „Tage“ an die Ausgabewerte an\",\n    \"daysName\": \"Tage Name\",\n    \"description\": \"Wandeln Sie Stunden mit anpassbaren Optionen in Tage um.\",\n    \"shortDescription\": \"Stunden in Tage umrechnen\",\n    \"title\": \"Stunden in Tage umrechnen\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie Stunden in Tage umrechnen. Sie können Stunden als Zahlen oder mit Einheiten eingeben, und das Tool rechnet sie in Tage um. Sie können den Ausgabewerten auch das Suffix „Tage“ anhängen.\",\n      \"title\": \"Stunden in Tage umrechnen\"\n    }\n  },\n  \"convertSecondsToTime\": {\n    \"addPadding\": \"Polsterung hinzufügen\",\n    \"addPaddingDescription\": \"Fügen Sie Stunden, Minuten und Sekunden mit Nullen aufgefüllt hinzu.\",\n    \"description\": \"Konvertiert Sekunden in ein lesbares Zeitformat (Stunden:Minuten:Sekunden). Geben Sie die Anzahl der Sekunden ein, um die formatierte Zeit zu erhalten.\",\n    \"shortDescription\": \"Sekunden in das Zeitformat konvertieren\",\n    \"timePadding\": \"Zeitauffüllung\",\n    \"title\": \"Sekunden in Zeit umrechnen\",\n    \"toolInfo\": {\n      \"title\": \"Was ist ein {{title}}?\"\n    }\n  },\n  \"convertTimeToSeconds\": {\n    \"description\": \"Konvertieren Sie die formatierte Zeit (HH:MM:SS) in Sekunden.\",\n    \"inputTitle\": \"Eingabezeit\",\n    \"resultTitle\": \"Sekunden\",\n    \"shortDescription\": \"Konvertieren Sie das Zeitformat in Sekunden\",\n    \"title\": \"Zeit in Sekunden umrechnen\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie formatierte Zeitzeichenfolgen (HH:MM:SS) in Sekunden umwandeln. Es ist nützlich für die Berechnung von Dauern und Zeitintervallen.\",\n      \"title\": \"Zeit in Sekunden umrechnen\"\n    }\n  },\n  \"convertUnixToDate\": {\n    \"addUtcLabel\": \"Suffix „UTC“ hinzufügen\",\n    \"addUtcLabelDescription\": \"Anzeige „UTC“ nach dem konvertierten Datum (nur für den UTC-Modus)\",\n    \"description\": \"Konvertieren Sie einen Unix-Zeitstempel in ein für Menschen lesbares Datum.\",\n    \"outputOptions\": \"Ausgabeoptionen\",\n    \"shortDescription\": \"Unix-Zeitstempel in Datum konvertieren\",\n    \"title\": \"Konvertieren Sie Unix in Datum\",\n    \"toolInfo\": {\n      \"description\": \"Dieses Tool konvertiert einen Unix-Zeitstempel (in Sekunden) in ein lesbares Datumsformat (z. B. JJJJ-MM-TT HH:MM:SS). Es unterstützt sowohl die lokale als auch die UTC-Ausgabe und eignet sich daher für die schnelle Interpretation von Zeitstempeln aus Protokollen, APIs oder Systemen, die Unix-Zeit verwenden.\",\n      \"title\": \"Konvertieren Sie Unix in Datum\"\n    },\n    \"useLocalTime\": \"Lokale Zeit verwenden\",\n    \"useLocalTimeDescription\": \"Zeigen Sie das konvertierte Datum in Ihrer lokalen Zeitzone anstelle von UTC an\",\n    \"withLabel\": \"Optionen\"\n  },\n  \"crontabGuru\": {\n    \"description\": \"Generieren und verstehen Sie Cron-Ausdrücke. Erstellen Sie Cron-Zeitpläne für automatisierte Aufgaben und Systemjobs.\",\n    \"shortDescription\": \"Cron-Ausdrücke generieren und verstehen\",\n    \"title\": \"Crontab Guru\"\n  },\n  \"timeBetweenDates\": {\n    \"description\": \"Berechnen Sie die Zeitdifferenz zwischen zwei Daten. Ermitteln Sie die genaue Dauer in Tagen, Stunden, Minuten und Sekunden.\",\n    \"endDate\": \"Enddatum\",\n    \"endDateTime\": \"Enddatum und -zeit\",\n    \"endTime\": \"Endzeit\",\n    \"endTimezone\": \"Endzeitzone\",\n    \"shortDescription\": \"Berechnen Sie die Zeit zwischen zwei Daten\",\n    \"startDate\": \"Startdatum\",\n    \"startDateTime\": \"Startdatum und -zeit\",\n    \"startTime\": \"Startzeit\",\n    \"startTimezone\": \"Startzeitzone\",\n    \"title\": \"Zeit zwischen den Terminen\",\n    \"toolInfo\": {\n      \"description\": \"Berechnen Sie die genaue Zeitdifferenz zwischen zwei Datums- und Uhrzeitangaben, mit Unterstützung für verschiedene Zeitzonen. Dieses Tool bietet eine detaillierte Aufschlüsselung der Zeitdifferenz in verschiedenen Einheiten (Jahre, Monate, Tage, Stunden, Minuten und Sekunden).\",\n      \"title\": \"Zeit zwischen Daten-Rechner\"\n    }\n  },\n  \"truncateClockTime\": {\n    \"description\": \"Kürzen Sie die Uhrzeit, um Sekunden oder Minuten zu entfernen. Runden Sie die Zeit auf die nächste Stunde, Minute oder ein benutzerdefiniertes Intervall.\",\n    \"printDroppedComponents\": \"Drucken gelöschter Komponenten\",\n    \"shortDescription\": \"Kürzen Sie die Uhrzeit auf die angegebene Genauigkeit\",\n    \"timePadding\": \"Zeitauffüllung\",\n    \"title\": \"Uhrzeit kürzen\",\n    \"toolInfo\": {\n      \"title\": \"Was ist ein {{title}}?\"\n    },\n    \"truncateMinutesAndSeconds\": \"Minuten und Sekunden kürzen\",\n    \"truncateMinutesAndSecondsDescription\": \"Lassen Sie beides weg – die Minuten- und Sekundenkomponenten jeder Uhrzeit.\",\n    \"truncateOnlySeconds\": \"Nur Sekunden abschneiden\",\n    \"truncateOnlySecondsDescription\": \"Entfernen Sie die Sekundenkomponente aus jeder Uhrzeit.\",\n    \"truncationSide\": \"Abschneideseite\",\n    \"useZeroPadding\": \"Null-Padding verwenden\",\n    \"zeroPaddingDescription\": \"Sorgen Sie dafür, dass alle Zeitkomponenten immer zweistellig sind.\",\n    \"zeroPrintDescription\": \"Die weggelassenen Teile werden als Nullwerte „00“ angezeigt.\",\n    \"zeroPrintTruncatedParts\": \"Abgeschnittene Teile ohne Druck\"\n  }\n}\n"
  },
  {
    "path": "public/locales/de/translation.json",
    "content": "{\n  \"audio\": {\n    \"changeSpeed\": {\n      \"description\": \"Ändern Sie die Wiedergabegeschwindigkeit von Audiodateien. Beschleunigen oder verlangsamen Sie die Wiedergabe, ohne die Tonhöhe zu verändern.\",\n      \"name\": \"Audiogeschwindigkeit ändern\",\n      \"shortDescription\": \"Ändern Sie die Geschwindigkeit von Audiodateien\"\n    },\n    \"extractAudio\": {\n      \"description\": \"Extrahieren Sie die Audiospur aus einer Videodatei und speichern Sie sie als separate Audiodatei im gewünschten Format (AAC, MP3, WAV).\",\n      \"name\": \"Audio extrahieren\",\n      \"shortDescription\": \"Extrahieren Sie Audio aus Videodateien (MP4, MOV usw.) in AAC, MP3 oder WAV.\"\n    }\n  },\n  \"baseFileInput\": {\n    \"copyFailed\": \"Kopieren fehlgeschlagen: {{error}}\",\n    \"dropFileHere\": \"Lassen Sie Ihre {{type}} Hier\",\n    \"fileCopied\": \"Datei kopiert\",\n    \"selectFileDescription\": \"Klicken Sie hier, um eine {{type}} Drücken Sie auf Ihrem Gerät Strg+V, um eine {{type}} aus der Zwischenablage oder ziehen Sie eine Datei per Drag & Drop vom Desktop\"\n  },\n  \"categories\": {\n    \"audio\": {\n      \"description\": \"Tools für die Arbeit mit Audio – Audio aus Video extrahieren, Audiogeschwindigkeit anpassen, mehrere Audiodateien zusammenführen und vieles mehr.\",\n      \"title\": \"Audio-Tools\"\n    },\n    \"csv\": {\n      \"description\": \"Tools zum Arbeiten mit CSV-Dateien – konvertieren Sie CSV in verschiedene Formate, bearbeiten Sie CSV-Daten, validieren Sie die CSV-Struktur und verarbeiten Sie CSV-Dateien effizient.\",\n      \"title\": \"CSV-Tools\"\n    },\n    \"gif\": {\n      \"description\": \"Tools zum Arbeiten mit GIF-Animationen – transparente GIFs erstellen, GIF-Frames extrahieren, Text zu GIFs hinzufügen, zuschneiden, drehen, GIFs umkehren und vieles mehr.\",\n      \"title\": \"GIF-Tools\"\n    },\n    \"image-generic\": {\n      \"description\": \"Tools zum Arbeiten mit Bildern – komprimieren, Größe ändern, zuschneiden, in JPG konvertieren, drehen, Hintergrund entfernen und vieles mehr.\",\n      \"title\": \"Bildwerkzeuge\"\n    },\n    \"json\": {\n      \"description\": \"Tools für die Arbeit mit JSON-Datenstrukturen – Verschönern und Minimieren von JSON-Objekten, Verflachen von JSON-Arrays, Stringifizieren von JSON-Werten, Analysieren von Daten und vieles mehr\",\n      \"title\": \"JSON-Tools\"\n    },\n    \"list\": {\n      \"description\": \"Tools zum Arbeiten mit Listen – Sortieren, Umkehren, Zufallssortieren von Listen, Suchen eindeutiger und doppelter Listenelemente, Ändern der Trennzeichen von Listenelementen und vieles mehr.\",\n      \"title\": \"Tools auflisten\"\n    },\n    \"number\": {\n      \"description\": \"Tools für die Arbeit mit Zahlen – Zahlenfolgen generieren, Zahlen in Wörter und Wörter in Zahlen umwandeln, sortieren, runden, Zahlen faktorisieren und vieles mehr.\",\n      \"title\": \"Zahlenwerkzeuge\"\n    },\n    \"pdf\": {\n      \"description\": \"Tools zum Arbeiten mit PDF-Dateien – Text aus PDFs extrahieren, PDFs in andere Formate konvertieren, PDFs bearbeiten und vieles mehr.\",\n      \"title\": \"PDF-Tools\"\n    },\n    \"png\": {\n      \"description\": \"Tools zum Arbeiten mit PNG-Bildern – konvertieren Sie PNGs in JPGs, erstellen Sie transparente PNGs, ändern Sie PNG-Farben, beschneiden Sie, drehen Sie, ändern Sie die Größe von PNGs und vieles mehr.\",\n      \"title\": \"PNG-Tools\"\n    },\n    \"seeAll\": \"Alles sehen {{title}}\",\n    \"string\": {\n      \"description\": \"Tools zum Arbeiten mit Text – Text in Bilder umwandeln, Text suchen und ersetzen, Text in Fragmente aufteilen, Textzeilen verbinden, Text wiederholen und vieles mehr.\",\n      \"title\": \"Textwerkzeuge\"\n    },\n    \"time\": {\n      \"description\": \"Tools zum Arbeiten mit Zeit und Datum – Zeitunterschiede berechnen, zwischen Zeitzonen umrechnen, Datumsangaben formatieren, Datumssequenzen generieren und vieles mehr.\",\n      \"title\": \"Zeitwerkzeuge\"\n    },\n    \"try\": \"Versuchen {{title}}\",\n    \"video\": {\n      \"description\": \"Tools zum Arbeiten mit Videos – Extrahieren Sie Frames aus Videos, erstellen Sie GIFs aus Videos, konvertieren Sie Videos in verschiedene Formate und vieles mehr.\",\n      \"title\": \"Video-Tools\"\n    },\n    \"xml\": {\n      \"description\": \"Tools zum Arbeiten mit XML-Datenstrukturen – Viewer, Beautifier, Validator und vieles mehr\",\n      \"title\": \"XML-Tools\"\n    }\n  },\n  \"csv\": {\n    \"findIncompleteCsvRecords\": {\n      \"description\": \"Laden Sie einfach Ihre CSV-Datei im untenstehenden Formular hoch. Das Tool prüft automatisch, ob in Zeilen und Spalten Werte fehlen. In den Tool-Optionen können Sie das Eingabedateiformat anpassen (Trennzeichen, Anführungszeichen und Kommentarzeichen festlegen). Zusätzlich können Sie die Prüfung auf leere Werte aktivieren, leere Zeilen überspringen und die Anzahl der Fehlermeldungen in der Ausgabe begrenzen.\",\n      \"name\": \"Unvollständige CSV-Datensätze finden\",\n      \"shortDescription\": \"Finden Sie schnell Zeilen und Spalten in CSV, in denen Werte fehlen.\"\n    }\n  },\n  \"hero\": {\n    \"brand\": \"OmniTools\",\n    \"description\": \"Steigern Sie Ihre Produktivität mit OmniTools, dem ultimativen Toolkit für schnelles Arbeiten! Greifen Sie direkt von Ihrem Browser aus auf Tausende benutzerfreundliche Dienstprogramme zum Bearbeiten von Bildern, Texten, Listen und Daten zu.\",\n    \"examples\": {\n      \"calculateNumberSum\": \"Zahlensumme berechnen\",\n      \"changeGifSpeed\": \"GIF-Geschwindigkeit ändern\",\n      \"compressPng\": \"PNG komprimieren\",\n      \"createTransparentImage\": \"Erstellen Sie ein transparentes Bild\",\n      \"prettifyJson\": \"JSON verschönern\",\n      \"sortList\": \"Sortieren einer Liste\",\n      \"splitPdf\": \"PDF teilen\",\n      \"splitText\": \"Einen Text teilen\",\n      \"trimVideo\": \"Video zuschneiden\"\n    },\n    \"searchPlaceholder\": \"Alle Tools durchsuchen\",\n    \"title\": \"Erledigen Sie Dinge schnell mit\"\n  },\n  \"inputFooter\": {\n    \"clear\": \"Klar\",\n    \"copyToClipboard\": \"In die Zwischenablage kopieren\",\n    \"importFromFile\": \"Aus Datei importieren\"\n  },\n  \"list\": {\n    \"group\": {\n      \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Gruppieren von Listenelementen. Geben Sie Ihre Liste ein und legen Sie Gruppierungskriterien fest, um Elemente in logische Gruppen zu ordnen. Ideal zum Kategorisieren von Daten, Organisieren von Informationen oder Erstellen strukturierter Listen. Unterstützt benutzerdefinierte Trennzeichen und verschiedene Gruppierungsoptionen.\",\n      \"name\": \"Gruppe\",\n      \"shortDescription\": \"Gruppieren Sie Listenelemente nach gemeinsamen Eigenschaften\"\n    },\n    \"reverse\": {\n      \"description\": \"Diese einfache browserbasierte Anwendung druckt alle Listenelemente rückwärts. Die Eingabeelemente können durch ein beliebiges Symbol getrennt werden. Sie können auch das Trennzeichen der umgekehrten Listenelemente ändern.\",\n      \"name\": \"Umkehren\",\n      \"shortDescription\": \"Schnelles Umkehren einer Liste\"\n    },\n    \"sort\": {\n      \"description\": \"Dies ist eine sehr einfache browserbasierte Anwendung, die Elemente in einer Liste sortiert und in auf- oder absteigender Reihenfolge anordnet. Sie können die Elemente alphabetisch, numerisch oder nach Länge sortieren. Sie können auch doppelte und leere Elemente entfernen sowie einzelne Elemente mit Leerzeichen kürzen. Sie können die Elemente der Eingabeliste durch ein beliebiges Trennzeichen oder alternativ durch einen regulären Ausdruck trennen. Zusätzlich können Sie ein neues Trennzeichen für die sortierte Ausgabeliste erstellen.\",\n      \"name\": \"Sortieren\",\n      \"shortDescription\": \"Schnelles Sortieren einer Liste\"\n    }\n  },\n  \"navbar\": {\n    \"buyMeACoffee\": \"Kauf mir einen Kaffee\",\n    \"hireMe\": \"Stellen Sie mich ein\",\n    \"home\": \"Heim\",\n    \"tools\": \"Werkzeuge\"\n  },\n  \"number\": {\n    \"generate\": {\n      \"description\": \"Berechnen Sie schnell eine Liste von Ganzzahlen in Ihrem Browser. Geben Sie dazu einfach die erste Ganzzahl an, ändern Sie den Wert und die Gesamtzahl in den Optionen unten. Das Dienstprogramm generiert dann die entsprechende Anzahl Ganzzahlen.\",\n      \"name\": \"Zahlen generieren\",\n      \"shortDescription\": \"Berechnen Sie schnell eine Liste von Ganzzahlen in Ihrem Browser\"\n    },\n    \"sum\": {\n      \"description\": \"Dies ist eine sehr einfache browserbasierte Anwendung zum Summieren von Zahlen. Die eingegebenen Zahlen können durch ein beliebiges Symbol getrennt werden. Sie können auch das Trennzeichen der summierten Zahlen ändern.\",\n      \"name\": \"Summenzahlen\",\n      \"shortDescription\": \"Schnelles Summieren einer Zahlenliste\"\n    }\n  },\n  \"numericInputWithUnit\": {\n    \"unit\": \"Einheit\"\n  },\n  \"pdf\": {\n    \"compressPdf\": {\n      \"description\": \"Reduzieren Sie die PDF-Dateigröße bei gleichbleibender Qualität mit Ghostscript\",\n      \"name\": \"PDF komprimieren\",\n      \"shortDescription\": \"Komprimieren Sie PDF-Dateien sicher in Ihrem Browser\"\n    },\n    \"mergePdf\": {\n      \"description\": \"Kombinieren Sie mehrere PDF-Dateien zu einem einzigen Dokument.\",\n      \"name\": \"PDF zusammenführen\",\n      \"shortDescription\": \"Mehrere PDF-Dateien zu einem einzigen Dokument zusammenführen\"\n    },\n    \"pdfToEpub\": {\n      \"description\": \"Wandeln Sie PDF-Dokumente in EPUB-Dateien um, um eine bessere E-Reader-Kompatibilität zu erzielen.\",\n      \"name\": \"PDF zu EPUB\",\n      \"shortDescription\": \"Konvertieren Sie PDF-Dateien in das EPUB-Format\"\n    },\n    \"protectPdf\": {\n      \"description\": \"Fügen Sie Ihren PDF-Dateien sicher in Ihrem Browser einen Passwortschutz hinzu\",\n      \"name\": \"PDF schützen\",\n      \"shortDescription\": \"PDF-Dateien sicher mit einem Passwort schützen\"\n    },\n    \"splitPdf\": {\n      \"description\": \"Extrahieren Sie bestimmte Seiten aus einer PDF-Datei mithilfe von Seitenzahlen oder Bereichen (z. B. 1,5-8).\",\n      \"name\": \"PDF teilen\",\n      \"shortDescription\": \"Extrahieren Sie bestimmte Seiten aus einer PDF-Datei\"\n    }\n  },\n  \"resultFooter\": {\n    \"copy\": \"In die Zwischenablage kopieren\",\n    \"download\": \"Herunterladen\"\n  },\n  \"string\": {\n    \"createPalindrome\": {\n      \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Erstellen von Palindromen aus beliebigem Text. Geben Sie Text ein und verwandeln Sie ihn sofort in ein Palindrom, das vorwärts und rückwärts gleich gelesen wird. Ideal für Wortspiele, das Erstellen symmetrischer Textmuster oder das Erkunden sprachlicher Kuriositäten.\",\n      \"name\": \"Palindrom erstellen\",\n      \"shortDescription\": \"Erstellen Sie Text, der vorwärts und rückwärts gleich gelesen wird\"\n    },\n    \"palindrome\": {\n      \"description\": \"Das weltweit einfachste browserbasierte Tool zur Überprüfung von Palindromen. Überprüfen Sie sofort, ob sich Ihr Text vorwärts und rückwärts gleich liest. Ideal für Worträtsel, linguistische Analysen oder die Validierung symmetrischer Textmuster. Unterstützt verschiedene Trennzeichen und die Erkennung mehrteiliger Palindrome.\",\n      \"name\": \"Palindrom\",\n      \"shortDescription\": \"Überprüfen Sie, ob der Text vorwärts und rückwärts gleich gelesen wird\"\n    },\n    \"repeat\": {\n      \"description\": \"Mit diesem Tool können Sie einen bestimmten Text mit einem optionalen Trennzeichen mehrmals wiederholen.\",\n      \"name\": \"Text wiederholen\",\n      \"shortDescription\": \"Text mehrmals wiederholen\"\n    },\n    \"reverse\": {\n      \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zum Umkehren von Text. Geben Sie beliebigen Text ein und lassen Sie ihn sofort Zeichen für Zeichen umkehren. Ideal zum Erstellen von Spiegeltext, Analysieren von Palindromen oder zum Experimentieren mit Textmustern. Leerzeichen und Sonderzeichen bleiben beim Umkehren erhalten.\",\n      \"name\": \"Umkehren\",\n      \"shortDescription\": \"Kehren Sie jeden Text Zeichen für Zeichen um\"\n    },\n    \"toMorse\": {\n      \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zur Konvertierung von Text in Morsecode. Laden Sie Ihren Text in das Eingabeformular links und Sie erhalten sofort Morsecode im Ausgabebereich. Leistungsstark, kostenlos und schnell. Text laden – Morsecode erhalten.\",\n      \"name\": \"Zeichenfolge zu Morse\",\n      \"shortDescription\": \"Text schnell in Morse kodieren\"\n    },\n    \"uppercase\": {\n      \"description\": \"Das weltweit einfachste browserbasierte Dienstprogramm zur Konvertierung von Text in Großbuchstaben. Geben Sie einfach Ihren Text ein, und er wird automatisch in Großbuchstaben umgewandelt. Ideal für Überschriften, Hervorhebungen oder die Standardisierung des Textformats. Unterstützt verschiedene Textformate und behält Sonderzeichen bei.\",\n      \"name\": \"Großbuchstaben\",\n      \"shortDescription\": \"Text in Großbuchstaben umwandeln\"\n    }\n  },\n  \"toolExamples\": {\n    \"subtitle\": \"Klicken Sie hier, um es auszuprobieren!\",\n    \"title\": \"{{title}} Beispiele\"\n  },\n  \"toolFileResult\": {\n    \"copied\": \"Datei kopiert\",\n    \"copyFailed\": \"Kopieren fehlgeschlagen: {{error}}\",\n    \"loading\": \"Wird geladen... Dies kann einen Moment dauern.\",\n    \"result\": \"Ergebnis\"\n  },\n  \"toolHeader\": {\n    \"seeExamples\": \"Beispiele ansehen\"\n  },\n  \"toolLayout\": {\n    \"allToolsTitle\": \"Alle {{type}}\"\n  },\n  \"toolMultiFileResult\": {\n    \"copied\": \"Datei kopiert\",\n    \"copyFailed\": \"Kopieren fehlgeschlagen: {{error}}\",\n    \"loading\": \"Wird geladen... Dies kann einen Moment dauern.\",\n    \"result\": \"Ergebnis\"\n  },\n  \"toolMultipleAudioInput\": {\n    \"inputTitle\": \"Eingang {{type}}\",\n    \"noFilesSelected\": \"Keine Dateien ausgewählt\"\n  },\n  \"toolMultiplePdfInput\": {\n    \"inputTitle\": \"Eingang {{type}}\",\n    \"noFilesSelected\": \"Keine Dateien ausgewählt\"\n  },\n  \"toolOptions\": {\n    \"title\": \"Werkzeugoptionen\"\n  },\n  \"toolTextInput\": {\n    \"copied\": \"Text kopiert\",\n    \"copyFailed\": \"Kopieren fehlgeschlagen: {{error}}\",\n    \"input\": \"Eingabetext\",\n    \"placeholder\": \"Geben Sie hier Ihren Text ein...\"\n  },\n  \"toolTextResult\": {\n    \"copied\": \"Text kopiert\",\n    \"copyFailed\": \"Kopieren fehlgeschlagen: {{error}}\",\n    \"loading\": \"Wird geladen... Dies kann einen Moment dauern.\",\n    \"result\": \"Ergebnis\"\n  },\n  \"userTypes\": {\n    \"developers\": \"Entwickler\",\n    \"generalUsers\": \"Allgemeine Benutzer\"\n  }\n}\n"
  },
  {
    "path": "public/locales/de/video.json",
    "content": "{\n  \"changeSpeed\": {\n    \"defaultMultiplier\": \"Standardmultiplikator: 2 bedeutet 2x schneller\",\n    \"description\": \"Ändern Sie die Wiedergabegeschwindigkeit von Videodateien. Beschleunigen oder verlangsamen Sie Videos, während die Audiosynchronisation erhalten bleibt. Unterstützt verschiedene Geschwindigkeitsmultiplikatoren und gängige Videoformate.\",\n    \"inputTitle\": \"Eingangsvideo\",\n    \"newVideoSpeed\": \"Neue Videogeschwindigkeit\",\n    \"resultTitle\": \"Bearbeitetes Video\",\n    \"settingSpeed\": \"Geschwindigkeit einstellen\",\n    \"shortDescription\": \"Ändern der Videowiedergabegeschwindigkeit\",\n    \"title\": \"Videogeschwindigkeit ändern\",\n    \"toolInfo\": {\n      \"title\": \"Was ist ein {{title}}?\"\n    }\n  },\n  \"compress\": {\n    \"default\": \"Standard\",\n    \"description\": \"Komprimieren Sie Videos, indem Sie sie auf verschiedene Auflösungen wie 240p, 480p, 720p usw. skalieren. Dieses Tool hilft, die Dateigröße zu reduzieren und gleichzeitig eine akzeptable Qualität beizubehalten. Unterstützt gängige Videoformate wie MP4, WebM und OGG.\",\n    \"inputTitle\": \"Eingangsvideo\",\n    \"loadingText\": \"Video wird komprimiert...\",\n    \"lossless\": \"Verlustfrei\",\n    \"quality\": \"Qualität (CRF)\",\n    \"resolution\": \"Auflösung\",\n    \"resultTitle\": \"Komprimiertes Video\",\n    \"shortDescription\": \"Komprimieren Sie Videos durch Skalieren auf verschiedene Auflösungen\",\n    \"title\": \"Video komprimieren\",\n    \"worst\": \"Am schlimmsten\"\n  },\n  \"cropVideo\": {\n    \"cropCoordinates\": \"Zuschneidekoordinaten\",\n    \"croppingVideo\": \"Video zuschneiden\",\n    \"description\": \"Schneiden Sie das Video zu, um unerwünschte Bereiche zu entfernen.\",\n    \"errorBeyondHeight\": \"Der Zuschneidebereich geht über die Videohöhe hinaus ({{height}}px)\",\n    \"errorBeyondWidth\": \"Der Zuschneidebereich geht über die Videobreite hinaus ({{width}}px)\",\n    \"errorCroppingVideo\": \"Fehler beim Zuschneiden des Videos. Bitte überprüfen Sie die Parameter und die Videodatei.\",\n    \"errorLoadingDimensions\": \"Videoabmessungen konnten nicht geladen werden\",\n    \"errorNonNegativeCoordinates\": \"X- und Y-Koordinaten dürfen nicht negativ sein\",\n    \"errorPositiveDimensions\": \"Breite und Höhe müssen positiv sein\",\n    \"height\": \"Höhe\",\n    \"inputTitle\": \"Eingangsvideo\",\n    \"loadVideoForDimensions\": \"Laden Sie ein Video, um die Abmessungen anzuzeigen\",\n    \"longDescription\": \"Mit diesem Tool können Sie Videodateien zuschneiden, um unerwünschte Bereiche zu entfernen oder bestimmte Videoteile hervorzuheben. Nützlich zum Entfernen schwarzer Balken, Anpassen des Seitenverhältnisses oder zum Fokussieren auf wichtige Inhalte. Unterstützt verschiedene Videoformate, darunter MP4, MOV und AVI.\",\n    \"resultTitle\": \"Zugeschnittenes Video\",\n    \"shortDescription\": \"Video zuschneiden, um unerwünschte Bereiche zu entfernen\",\n    \"title\": \"Video zuschneiden\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie Videodateien zuschneiden, um unerwünschte Bereiche zu entfernen. Sie können den Zuschneidebereich festlegen, indem Sie die X- und Y-Koordinaten sowie die Breiten- und Höhenmaße festlegen.\",\n      \"title\": \"Video zuschneiden\"\n    },\n    \"videoDimensions\": \"Videoabmessungen: {{width}} × {{height}} Pixel\",\n    \"videoInformation\": \"Videoinformationen\",\n    \"width\": \"Breite\",\n    \"xCoordinate\": \"X (links)\",\n    \"yCoordinate\": \"Y (oben)\"\n  },\n  \"flip\": {\n    \"description\": \"Drehen Sie Videodateien horizontal oder vertikal. Spiegeln Sie Videos für Spezialeffekte oder zum Korrigieren von Ausrichtungsproblemen.\",\n    \"flippingVideo\": \"Video spiegeln\",\n    \"horizontalLabel\": \"Horizontal (Spiegel)\",\n    \"inputTitle\": \"Eingangsvideo\",\n    \"orientation\": \"Orientierung\",\n    \"resultTitle\": \"Gespiegeltes Video\",\n    \"shortDescription\": \"Video horizontal oder vertikal spiegeln\",\n    \"title\": \"Video umdrehen\",\n    \"verticalLabel\": \"Vertikal (auf dem Kopf stehend)\"\n  },\n  \"gif\": {\n    \"changeSpeed\": {\n      \"description\": \"Ändern Sie die Wiedergabegeschwindigkeit von GIF-Animationen. Beschleunigen oder verlangsamen Sie GIFs, ohne die Animation zu verlangsamen.\",\n      \"shortDescription\": \"Ändern Sie die Geschwindigkeit der GIF-Animation\",\n      \"title\": \"GIF-Geschwindigkeit ändern\"\n    }\n  },\n  \"loop\": {\n    \"description\": \"Erstellen Sie ein Loop-Video, indem Sie das Originalvideo mehrmals wiederholen.\",\n    \"inputTitle\": \"Eingangsvideo\",\n    \"loopingVideo\": \"Videoschleife\",\n    \"loops\": \"Schleifen\",\n    \"numberOfLoops\": \"Anzahl der Schleifen\",\n    \"resultTitle\": \"Videoschleife\",\n    \"shortDescription\": \"Erstellen Sie Loop-Videodateien\",\n    \"title\": \"Video in Endlosschleife\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie ein Loop-Video erstellen, indem Sie das Originalvideo mehrmals wiederholen. Sie können festlegen, wie oft das Video wiederholt werden soll.\",\n      \"title\": \"Was ist ein {{title}}?\"\n    }\n  },\n  \"mergeVideo\": {\n    \"description\": \"Kombinieren Sie mehrere Videodateien zu einem fortlaufenden Video.\",\n    \"longDescription\": \"Mit diesem Tool können Sie mehrere Videodateien zu einem einzigen zusammenhängenden Video zusammenfügen oder anhängen. Laden Sie einfach Ihre Videodateien hoch, ordnen Sie sie in der gewünschten Reihenfolge an und fügen Sie sie zu einer Datei zusammen, um sie einfach zu teilen oder zu bearbeiten.\",\n    \"shortDescription\": \"Videos einfach anhängen und zusammenführen.\",\n    \"title\": \"Videos zusammenführen\"\n  },\n  \"rotate\": {\n    \"180Degrees\": \"180° (auf den Kopf gestellt)\",\n    \"270Degrees\": \"270° (90° gegen den Uhrzeigersinn)\",\n    \"90Degrees\": \"90° im Uhrzeigersinn\",\n    \"description\": \"Drehen Sie Videodateien um 90, 180 oder 270 Grad. Korrigieren Sie die Videoausrichtung oder erstellen Sie Spezialeffekte mit präziser Rotationssteuerung.\",\n    \"inputTitle\": \"Eingangsvideo\",\n    \"resultTitle\": \"Gedrehtes Video\",\n    \"rotatingVideo\": \"Rotierendes Video\",\n    \"rotation\": \"Drehung\",\n    \"shortDescription\": \"Video um angegebene Gradzahlen drehen\",\n    \"title\": \"Video drehen\"\n  },\n  \"trim\": {\n    \"description\": \"Kürzen Sie Videodateien, indem Sie Start- und Endzeiten festlegen. Entfernen Sie unerwünschte Abschnitte am Anfang oder Ende von Videos.\",\n    \"endTime\": \"Endzeit\",\n    \"inputTitle\": \"Eingangsvideo\",\n    \"resultTitle\": \"Getrimmtes Video\",\n    \"shortDescription\": \"Schneiden Sie das Video, indem Sie unerwünschte Abschnitte entfernen\",\n    \"startTime\": \"Startzeit\",\n    \"timestamps\": \"Zeitstempel\",\n    \"title\": \"Video trimmen\"\n  },\n  \"videoToGif\": {\n    \"description\": \"Konvertieren Sie Videodateien in das animierte GIF-Format. Extrahieren Sie bestimmte Zeitbereiche und erstellen Sie gemeinsam nutzbare animierte Bilder.\",\n    \"shortDescription\": \"Video in animiertes GIF konvertieren\",\n    \"title\": \"Video zu GIF\"\n  }\n}\n"
  },
  {
    "path": "public/locales/de/xml.json",
    "content": "{\n  \"xmlBeautifier\": {\n    \"description\": \"Formatieren Sie XML mit den richtigen Einrückungen und Abständen.\",\n    \"indentation\": \"Vertiefung\",\n    \"inputTitle\": \"XML-Eingabe\",\n    \"resultTitle\": \"Verschönertes XML\",\n    \"shortDescription\": \"XML-Code formatieren und verschönern\",\n    \"title\": \"XML-Verschönerer\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie XML-Daten mit den richtigen Einrückungen und Abständen formatieren, sodass sie besser lesbar und einfacher zu bearbeiten sind.\",\n      \"title\": \"XML-Verschönerer\"\n    },\n    \"useSpaces\": \"Räume verwenden\",\n    \"useSpacesDescription\": \"Ausgabe mit Leerzeichen einrücken\",\n    \"useTabs\": \"Verwenden von Registerkarten\",\n    \"useTabsDescription\": \"Einrücken der Ausgabe mit Tabulatoren.\"\n  },\n  \"xmlValidator\": {\n    \"description\": \"Validieren Sie die XML-Syntax und -Struktur.\",\n    \"placeholder\": \"XML hier einfügen oder importieren ...\",\n    \"shortDescription\": \"XML-Code auf Fehler validieren\",\n    \"title\": \"XML-Validator\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie die XML-Syntax und -Struktur validieren. Es prüft die korrekte XML-Formatierung und gibt bei festgestellten Problemen detaillierte Fehlermeldungen aus.\",\n      \"title\": \"XML-Validator\"\n    }\n  },\n  \"xmlViewer\": {\n    \"description\": \"Zeigen Sie die XML-Struktur in einem Baumformat an und erkunden Sie sie.\",\n    \"inputTitle\": \"XML-Eingabe\",\n    \"resultTitle\": \"XML-Strukturansicht\",\n    \"title\": \"XML-Viewer\",\n    \"toolInfo\": {\n      \"description\": \"Mit diesem Tool können Sie XML-Daten in einem hierarchischen Baumformat anzeigen, wodurch die Untersuchung und das Verständnis der Struktur von XML-Dokumenten erleichtert wird.\",\n      \"title\": \"XML-Viewer\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/en/audio.json",
    "content": "{\n  \"changeSpeed\": {\n    \"description\": \"Change the playback speed of audio files. Speed up or slow down audio while maintaining pitch.\",\n    \"inputTitle\": \"Input Audio\",\n    \"newAudioSpeed\": \"New Audio Speed\",\n    \"outputFormat\": \"Output Format\",\n    \"resultTitle\": \"Edited Audio\",\n    \"settingSpeed\": \"Setting Speed\",\n    \"shortDescription\": \"Change the speed of audio files\",\n    \"speedDescription\": \"Default multiplier: 2 means 2x faster\",\n    \"title\": \"Change audio speed\",\n    \"toolInfo\": {\n      \"title\": \"What is {{title}}?\"\n    }\n  },\n  \"extractAudio\": {\n    \"description\": \"Extract audio track from video files.\",\n    \"extractingAudio\": \"Extracting Audio\",\n    \"inputTitle\": \"Input Video\",\n    \"outputFormat\": \"Output Format\",\n    \"outputFormatDescription\": \"Select the format for the audio to be extracted as.\",\n    \"resultTitle\": \"Extracted Audio\",\n    \"shortDescription\": \"Extract audio from video files (MP4, MOV, etc.) to AAC, MP3, or WAV.\",\n    \"title\": \"Extract Audio from Video\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to extract the audio track from video files. You can choose from different audio formats including AAC, MP3, and WAV.\",\n      \"title\": \"What is {{title}}?\"\n    }\n  },\n  \"mergeAudio\": {\n    \"description\": \"Combine multiple audio files into a single audio file by concatenating them in sequence.\",\n    \"inputTitle\": \"Input Audio Files\",\n    \"longDescription\": \"This tool allows you to merge multiple audio files into a single file by concatenating them in the order you upload them. Perfect for combining podcast segments, music tracks, or any audio files that need to be joined together. Supports various audio formats including MP3, AAC, and WAV.\",\n    \"mergingAudio\": \"Merging Audio\",\n    \"outputFormat\": \"Output Format\",\n    \"resultTitle\": \"Merged Audio\",\n    \"shortDescription\": \"Merge multiple audio files into one (MP3, AAC, WAV).\",\n    \"title\": \"Merge Audio\",\n    \"toolInfo\": {\n      \"title\": \"What is {{title}}?\"\n    }\n  },\n  \"trim\": {\n    \"description\": \"Cut and trim audio files to extract specific segments by specifying start and end times.\",\n    \"endTime\": \"End Time\",\n    \"endTimeDescription\": \"End time in format HH:MM:SS (e.g., 00:01:30)\",\n    \"inputTitle\": \"Input Audio\",\n    \"longDescription\": \"This tool allows you to trim audio files by specifying start and end times. You can extract specific segments from longer audio files, remove unwanted parts, or create shorter clips. Supports various audio formats including MP3, AAC, and WAV. Perfect for podcast editing, music production, or any audio editing needs.\",\n    \"outputFormat\": \"Output Format\",\n    \"resultTitle\": \"Trimmed Audio\",\n    \"shortDescription\": \"Trim audio files to extract specific time segments (MP3, AAC, WAV).\",\n    \"startTime\": \"Start Time\",\n    \"startTimeDescription\": \"Start time in format HH:MM:SS (e.g., 00:00:30)\",\n    \"timeSettings\": \"Time Settings\",\n    \"title\": \"Trim Audio\",\n    \"toolInfo\": {\n      \"title\": \"What is {{title}}?\"\n    },\n    \"trimmingAudio\": \"Trimming Audio\"\n  } \n}\n"
  },
  {
    "path": "public/locales/en/converters.json",
    "content": "{\n  \"audioConverter\": {\n    \"title\": \"Audio Converter\",\n    \"description\": \"Convert audio files between different formats.\",\n    \"shortDescription\": \"Convert audio files to various formats.\",\n    \"longDescription\": \"This tool allows you to convert audio files from one format to another, supporting a wide range of audio formats for seamless conversion.\",\n    \"outputFormat\": \"output Format\",\n    \"outputFormatDescription\": \"Select the desired output audio format\",\n    \"inputTitle\": \"Audio Input\",\n    \"outputTitle\": \"Converted Audio\"\n  }\n}\n"
  },
  {
    "path": "public/locales/en/csv.json",
    "content": "{\n  \"changeCsvSeparator\": {\n    \"description\": \"Change the delimiter/separator in CSV files. Convert between different CSV formats like comma, semicolon, tab, or custom separators.\",\n    \"shortDescription\": \"Change CSV file delimiter\",\n    \"title\": \"Change CSV Separator\"\n  },\n  \"csvRowsToColumns\": {\n    \"description\": \"This tool converts rows of a CSV (Comma Separated Values) file into columns. It extracts the horizontal lines from the input CSV one by one, rotates them 90 degrees, and outputs them as vertical columns one after another, separated by commas.', longDescription: 'This tool converts rows of a CSV (Comma Separated Values) file into columns. For example, if the input CSV data has 6 rows, then the output will have 6 columns and the elements of the rows will be arranged from the top to bottom. In a well-formed CSV, the number of values in each row is the same. However, in cases when rows are missing fields, the program can fix them and you can choose from the available options: fill missing data with empty elements or replace missing data with custom elements, such as \\\"missing\\\", \\\"?\\\", or \\\"x\\\". During the conversion process, the tool also cleans the CSV file from unnecessary information, such as empty lines (these are lines without visible information) and comments. To help the tool correctly identify comments, in the options, you can specify the symbol at the beginning of a line that starts a comment. This symbol is typically a hash \\\"#\\\" or double slash \\\"//\\\". Csv-abulous!.\",\n    \"longDescription\": \"This tool converts rows of a CSV (Comma Separated Values) file into columns. For example, if the input CSV data has 6 rows, then the output will have 6 columns and the elements of the rows will be arranged from the top to bottom. In a well-formed CSV, the number of values in each row is the same. However, in cases when rows are missing fields, the program can fix them and you can choose from the available options: fill missing data with empty elements or replace missing data with custom elements, such as\",\n    \"shortDescription\": \"Convert CSV rows to columns.\",\n    \"title\": \"Convert CSV Rows to Columns\"\n  },\n  \"csvToJson\": {\n    \"columnSeparator\": \"Column Separator (e.g., , ; \\\\t)\",\n    \"commentSymbol\": \"Comment Symbol (e.g., #)\",\n    \"conversionOptions\": \"Conversion Options\",\n    \"description\": \"Convert CSV files to JSON format with customizable options for delimiters, quotes, and output formatting. Support for headers, comments, and dynamic type conversion.\",\n    \"dynamicTypes\": \"Dynamic Types\",\n    \"dynamicTypesDescription\": \"Automatically convert numbers and booleans\",\n    \"error\": \"Error\",\n    \"errorParsing\": \"Error parsing CSV: {{error}}\",\n    \"fieldQuote\": \"Field Quote (e.g., \\\")\",\n    \"inputCsvFormat\": \"Input CSV Format\",\n    \"inputTitle\": \"Input CSV\",\n    \"invalidCsvFormat\": \"Invalid CSV format\",\n    \"resultTitle\": \"Output JSON\",\n    \"shortDescription\": \"Convert CSV data to JSON format.\",\n    \"skipEmptyLines\": \"Skip Empty Lines\",\n    \"skipEmptyLinesDescription\": \"Ignore empty lines in the input CSV\",\n    \"title\": \"Convert CSV to JSON\",\n    \"toolInfo\": {\n      \"description\": \"This tool transforms Comma Separated Values (CSV) files to JavaScript Object Notation (JSON) data structures. It supports various CSV formats with customizable delimiters, quote characters, and comment symbols. The converter can treat the first row as headers, skip empty lines, and automatically detect data types like numbers and booleans. The resulting JSON can be used for data migration, backups, or as input for other applications.\",\n      \"title\": \"What Is a CSV to JSON Converter?\"\n    },\n    \"useHeaders\": \"Use Headers\",\n    \"useHeadersDescription\": \"Treat the first row as column headers\"\n  },\n  \"csvToTsv\": {\n    \"description\": \"Upload your CSV file in the form below and it will automatically get converted to a TSV file. In the tool options, you can customize the input CSV format – specify the field delimiter, quotation character, and comment symbol, as well as skip empty CSV lines, and choose whether to preserve CSV column headers.\",\n    \"longDescription\": \"This tool transforms Comma Separated Values (CSV) data to Tab Separated Values (TSV) data. Both CSV and TSV are popular file formats for storing tabular data but they use different delimiters to separate values – CSV uses commas (\",\n    \"shortDescription\": \"Convert CSV data to TSV format.\",\n    \"title\": \"Convert CSV to TSV\"\n  },\n  \"csvToXml\": {\n    \"description\": \"Convert CSV files to XML format with customizable options.\",\n    \"shortDescription\": \"Convert CSV data to XML format.\",\n    \"title\": \"Convert CSV to XML\"\n  },\n  \"csvToYaml\": {\n    \"description\": \"Just upload your CSV file in the form below and it will automatically get converted to a YAML file. In the tool options, you can specify the field delimiter character, field quote character, and comment character to adapt the tool to custom CSV formats. Additionally, you can select the output YAML format: one that preserves CSV headers or one that excludes CSV headers.\",\n    \"longDescription\": \"This tool transforms CSV (Comma Separated Values) data into the YAML (Yet Another Markup Language) data. CSV is a simple, tabular format that is used to represent matrix-like data types consisting of rows and columns. YAML, on the other hand, is a more advanced format (actually a superset of JSON), which creates more human-readable data for serialization, and it supports lists, dictionaries, and nested objects. This program supports various input CSV formats – the input data can be comma-separated (default), semicolon-separated, pipe-separated, or use another completely different delimiter. You can specify the exact delimiter your data uses in the options. Similarly, in the options, you can specify the quote character that is used to wrap CSV fields (by default a double-quote symbol). You can also skip lines that start with comments by specifying the comment symbols in the options. This allows you to keep your data clean by skipping unnecessary lines. There are two ways to convert CSV to YAML. The first method converts each CSV row into a YAML list. The second method extracts headers from the first CSV row and creates YAML objects with keys based on these headers. You can also customize the output YAML format by specifying the number of spaces for indenting YAML structures. If you need to perform the reverse conversion, that is, transform YAML into CSV, you can use our Convert YAML to CSV tool. Csv-abulous!\",\n    \"shortDescription\": \"Quickly convert a CSV file to a YAML file.\",\n    \"title\": \"Convert CSV to YAML\"\n  },\n  \"findIncompleteCsvRecords\": {\n    \"checkingOptions\": \"Checking Options\",\n    \"commentCharacterDescription\": \"Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.\",\n    \"csvInputOptions\": \"CSV Input Options\",\n    \"csvSeparatorDescription\": \"Enter the character used to delimit columns in the CSV input file.\",\n    \"deleteLinesWithNoData\": \"Delete Lines with No Data\",\n    \"deleteLinesWithNoDataDescription\": \"Remove empty lines from CSV input file.\",\n    \"description\": \"Just upload your CSV file in the form below and this tool will automatically check if none of the rows or columns are missing values. In the tool options, you can adjust the input file format (specify the delimiter, quote character, and comment character). Additionally, you can enable checking for empty values, skip empty lines, and set a limit on the number of error messages in the output.\",\n    \"findEmptyValues\": \"Find Empty Values\",\n    \"findEmptyValuesDescription\": \"Display a message about CSV fields that are empty (These are not missing fields but fields that contain nothing).\",\n    \"inputTitle\": \"Input CSV\",\n    \"limitNumberOfMessages\": \"Limit number of messages\",\n    \"messageLimitDescription\": \"Set the limit of number of messages in the output.\",\n    \"quoteCharacterDescription\": \"Enter the quote character used to quote the CSV input fields.\",\n    \"resultTitle\": \"CSV Status\",\n    \"shortDescription\": \"Quickly find rows and columns in CSV that are missing values.\",\n    \"title\": \"Find incomplete CSV records\",\n    \"toolInfo\": {\n      \"title\": \"What is a {{title}}?\"\n    }\n  },\n  \"insertCsvColumns\": {\n    \"appendColumns\": \"Append columns\",\n    \"commentCharacterDescription\": \"Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.\",\n    \"csvOptions\": \"CSV Options\",\n    \"csvSeparator\": \"CSV separator\",\n    \"csvToInsert\": \"CSV to insert\",\n    \"csvToInsertDescription\": \"Enter one or more columns you want to insert into the CSV. the character used to delimit columns has to be the same with the one in the CSV input file. Ps: Blank lines will be ignored\",\n    \"customFillDescription\": \"If the input CSV file is incomplete (missing values), then add empty fields or custom symbols to records to make a well-formed CSV?\",\n    \"customFillValueDescription\": \"Use this custom value to fill in missing fields. (Works only with \\\"Custom Values\\\" mode above.)\",\n    \"customPosition\": \"Custom position\",\n    \"customPositionOptionsDescription\": \"Select the method to insert the columns in the CSV file.\",\n    \"description\": \"Add new columns to CSV data at specified positions.\",\n    \"fillWithCustomValues\": \"Fill With Customs Values\",\n    \"fillWithEmptyValues\": \"Fill With Empty Values\",\n    \"headerName\": \"Header name\",\n    \"headerNameDescription\": \"Header of the column you want to insert columns after.\",\n    \"inputTitle\": \"Input CSV\",\n    \"insertingPositionDescription\": \"Specify where to insert the columns in the CSV file.\",\n    \"position\": \"Position\",\n    \"positionOptions\": \"Position Options\",\n    \"prependColumns\": \"Prepend columns\",\n    \"quoteCharDescription\": \"Enter the quote character used to quote the CSV input fields.\",\n    \"resultTitle\": \"Output CSV\",\n    \"rowNumberDescription\": \"Number of the column you want to insert columns after.\",\n    \"separatorDescription\": \"Enter the character used to delimit columns in the CSV input file.\",\n    \"shortDescription\": \"Quickly insert one or more new columns anywhere in a CSV file.\",\n    \"title\": \"Insert CSV columns\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to insert new columns into CSV data at specified positions. You can prepend, append, or insert columns at custom positions based on header names or column numbers.\",\n      \"title\": \"Insert CSV Columns\"\n    }\n  },\n  \"swapCsvColumns\": {\n    \"description\": \"Just upload your CSV file in the form below, specify the columns to swap, and the tool will automatically change the positions of the specified columns in the output file. In the tool options, you can specify the column positions or names that you want to swap, as well as fix incomplete data and optionally remove empty records and records that have been commented out.\",\n    \"longDescription\": \"This tool reorganizes CSV data by swapping the positions of its columns. Swapping columns can enhance the readability of a CSV file by placing frequently used data together or in the front for easier data comparison and editing. For example, you can swap the first column with the last or swap the second column with the third. To swap columns based on their positions, select the\",\n    \"shortDescription\": \"Reorder CSV columns.\",\n    \"title\": \"Swap CSV Columns\"\n  },\n  \"transposeCsv\": {\n    \"description\": \"Just upload your CSV file in the form below, and this tool will automatically transpose your CSV. In the tool options, you can specify the character that starts the comment lines in the CSV to remove them. Additionally, if the CSV is incomplete (missing values), you can replace missing values with the empty character or a custom character.\",\n    \"longDescription\": \"This tool transposes Comma Separated Values (CSV). It treats the CSV as a matrix of data and flips all elements across the main diagonal. The output contains the same CSV data as the input, but now all the rows have become columns, and all the columns have become rows. After transposition, the CSV file will have opposite dimensions. For example, if the input file has 4 columns and 3 rows, the output file will have 3 columns and 4 rows. During conversion, the program also cleans the data from unnecessary lines and corrects incomplete data. Specifically, the tool automatically deletes all empty records and comments that begin with a specific character, which you can set in the option. Additionally, in cases where the CSV data is corrupted or lost, the utility completes the file with empty fields or custom fields that can be specified in the options. Csv-abulous!\",\n    \"shortDescription\": \"Quickly transpose a CSV file.\",\n    \"title\": \"Transpose CSV\"\n  },\n  \"tsvToJson\": {\n    \"description\": \"Convert TSV (Tab-Separated Values) data to JSON format. Transform tabular data into structured JSON objects.\",\n    \"shortDescription\": \"Convert TSV to JSON format\",\n    \"title\": \"TSV to JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/en/image.json",
    "content": "{\n  \"changeColors\": {\n    \"description\": \"World\",\n    \"shortDescription\": \"Quickly swap colors in a image\",\n    \"title\": \"Change colors in image\"\n  },\n  \"changeOpacity\": {\n    \"description\": \"Easily adjust the transparency of your images. Simply upload your image, use the slider to set the desired opacity level between 0 (fully transparent) and 1 (fully opaque), and download the modified image.\",\n    \"shortDescription\": \"Adjust transparency of images\",\n    \"title\": \"Change image Opacity\"\n  },\n  \"compress\": {\n    \"compressedSize\": \"Total compressed size\",\n    \"compressionOptions\": \"Compression options\",\n    \"compressing\": \"Compressing images...\",\n    \"description\": \"Reduce the file size of one or multiple images while preserving visual quality. Upload a batch of images and download them all at once as a ZIP archive.\",\n    \"failedToCompress\": \"Image(s) compression failed. Please try again.\",\n    \"someFailedToCompress\": \"Some images could not be compressed and were skipped.\",\n    \"fileSizes\": \"File sizes\",\n    \"inputTitle\": \"Images Input\",\n    \"maxFileSizeDescription\": \"Maximum output file size per image in megabytes\",\n    \"originalSize\": \"Total original size\",\n    \"qualityDescription\": \"Image quality percentage — lower value means smaller file size\",\n    \"resultTitle\": \"Compressed images\",\n    \"shortDescription\": \"Compress one or multiple images at once. Download results individually or as a ZIP archive.\",\n    \"title\": \"Compress Images\"\n  },\n  \"compressPng\": {\n    \"description\": \"This is a program that compresses PNG pictures. As soon as you paste your PNG picture in the input area, the program will compress it and show the result in the output area. In the options, you can adjust the compression level, as well as find the old and new picture file sizes.\",\n    \"shortDescription\": \"Quickly compress a PNG\",\n    \"title\": \"Compress png\"\n  },\n  \"convertJgpToPng\": {\n    \"description\": \"Quickly convert your JPG images to PNG. Just import your PNG image in the editor on the left\",\n    \"shortDescription\": \"Quickly convert your JPG images to PNG\",\n    \"title\": \"Convert JPG to PNG\"\n  },\n  \"convertToJpg\": {\n    \"description\": \"Convert various image formats (PNG, GIF, TIF, PSD, SVG, WEBP, HEIC, RAW) to JPG with customizable quality and background color settings.\",\n    \"shortDescription\": \"Convert images to JPG with quality control\",\n    \"title\": \"Convert Images to JPG\"\n  },\n  \"createTransparent\": {\n    \"description\": \"World\",\n    \"shortDescription\": \"Quickly make an image transparent\",\n    \"title\": \"Create transparent PNG\"\n  },\n  \"crop\": {\n    \"description\": \"Crop images to remove unwanted areas.\",\n    \"inputTitle\": \"Input image\",\n    \"resultTitle\": \"Cropped image\",\n    \"shortDescription\": \"Crop images quickly.\",\n    \"title\": \"Crop Image\"\n  },\n  \"editor\": {\n    \"description\": \"Advanced image editor with tools for cropping, rotating, annotating, adjusting colors, and adding watermarks. Edit your images with professional-grade tools directly in your browser.\",\n    \"shortDescription\": \"Edit images with advanced tools and features\",\n    \"title\": \"Image Editor\"\n  },\n  \"imageToText\": {\n    \"description\": \"Extract text from images (JPG, PNG) using optical character recognition (OCR).\",\n    \"shortDescription\": \"Extract text from images using OCR.\",\n    \"title\": \"Image to Text (OCR)\"\n  },\n  \"qrCode\": {\n    \"description\": \"Generate QR codes for different data types: URL, Text, Email, Phone, SMS, WiFi, vCard, and more.\",\n    \"shortDescription\": \"Create customized QR codes for various data formats.\",\n    \"title\": \"QR Code Generator\",\n    \"resultOutput\": \"Generated QR code\",\n    \"options\": {\n      \"qrType\": \"Select QR Code Type\",\n      \"encryptionType\": \"Encryption Type\",\n      \"email\": \"Email\",\n      \"emailBody\": \"Email Body (optional)\",\n      \"emailSubject\": \"Email Subject (optional)\",\n      \"text\": \"Enter the text\",\n      \"name\": \"Name\",\n      \"fullName\": \"Full Name\",\n      \"address\": \"Address\",\n      \"company\": \"Company (optional)\",\n      \"job\": \"Job Title (optional)\",\n      \"website\": \"Website (optional)\",\n      \"message\": \"Message (optional)\",\n      \"phone\": \"Phone Number\",\n      \"url\": \"Enter the URL\",\n      \"ssid\": \"Network Name (SSID)\",\n      \"password\": \"Password\",\n      \"size\": \"Size in pixels (100-1000)\"\n    }\n  },\n  \"removeBackground\": {\n    \"description\": \"World\",\n    \"shortDescription\": \"Automatically remove backgrounds from images\",\n    \"title\": \"Remove Background from Image\"\n  },\n  \"resize\": {\n    \"description\": \"Resize images to different dimensions.\",\n    \"dimensionType\": \"Dimension Type\",\n    \"heightDescription\": \"Height (in pixels)\",\n    \"inputTitle\": \"Input Image\",\n    \"maintainAspectRatio\": \"Maintain Aspect Ratio\",\n    \"maintainAspectRatioDescription\": \"Maintain the original aspect ratio of the image.\",\n    \"percentage\": \"Percentage\",\n    \"percentageDescription\": \"Percentage of original size (e.g., 50 for half size, 200 for double size)\",\n    \"resizeByPercentage\": \"Resize by Percentage\",\n    \"resizeByPercentageDescription\": \"Resize by specifying a percentage of the original size.\",\n    \"resizeByPixels\": \"Resize by Pixels\",\n    \"resizeByPixelsDescription\": \"Resize by specifying dimensions in pixels.\",\n    \"resizeMethod\": \"Resize Method\",\n    \"resultTitle\": \"Resized Image\",\n    \"setHeight\": \"Set Height\",\n    \"setHeightDescription\": \"Specify the height in pixels and calculate width based on aspect ratio.\",\n    \"setWidth\": \"Set Width\",\n    \"setWidthDescription\": \"Specify the width in pixels and calculate height based on aspect ratio.\",\n    \"shortDescription\": \"Resize images easily.\",\n    \"title\": \"Resize Image\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to resize JPG, PNG, SVG, or GIF images. You can resize by specifying dimensions in pixels or by percentage, with options to maintain the original aspect ratio.\",\n      \"title\": \"Resize Image\"\n    },\n    \"widthDescription\": \"Width (in pixels)\"\n  },\n  \"rotate\": {\n    \"description\": \"Rotate an image by a specified angle.\",\n    \"shortDescription\": \"Rotate an image easily.\",\n    \"title\": \"Rotate Image\"\n  }\n}\n"
  },
  {
    "path": "public/locales/en/json.json",
    "content": "{\n  \"comparison\": {\n    \"description\": \"Compare two JSON objects to identify differences in structure and values.\",\n    \"shortDescription\": \"Find differences between two JSON objects\",\n    \"title\": \"Compare JSON\"\n  },\n  \"escapeJson\": {\n    \"description\": \"Escape special characters in JSON strings. Convert JSON data to properly escaped format for safe transmission or storage.\",\n    \"shortDescription\": \"Escape special characters in JSON\",\n    \"title\": \"Escape JSON\"\n  },\n  \"jsonToXml\": {\n    \"description\": \"Convert JSON data to XML format. Transform structured JSON objects into well-formed XML documents.\",\n    \"shortDescription\": \"Convert JSON to XML format\",\n    \"title\": \"JSON to XML\"\n  },\n  \"minify\": {\n    \"description\": \"Remove all unnecessary whitespace from JSON.\",\n    \"inputTitle\": \"Input JSON\",\n    \"resultTitle\": \"Minified JSON\",\n    \"shortDescription\": \"Minify JSON by removing whitespace\",\n    \"title\": \"Minify JSON\",\n    \"toolInfo\": {\n      \"description\": \"JSON minification is the process of removing all unnecessary whitespace characters from JSON data while maintaining its validity. This includes removing spaces, newlines, and indentation that aren't required for the JSON to be parsed correctly. Minification reduces the size of JSON data, making it more efficient for storage and transmission while keeping the exact same data structure and values.\",\n      \"title\": \"What Is JSON Minification?\"\n    }\n  },\n  \"prettify\": {\n    \"description\": \"Format JSON with proper indentation and spacing.\",\n    \"indentation\": \"Indentation\",\n    \"inputTitle\": \"Input JSON\",\n    \"resultTitle\": \"Prettified JSON\",\n    \"shortDescription\": \"Format and beautify JSON code\",\n    \"title\": \"Prettify JSON\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to format JSON data with proper indentation and spacing, making it more readable and easier to work with.\",\n      \"title\": \"Prettify JSON\"\n    },\n    \"useSpaces\": \"Use Spaces\",\n    \"useSpacesDescription\": \"Indent output with spaces\",\n    \"useTabs\": \"Use Tabs\",\n    \"useTabsDescription\": \"Indent output with tabs.\"\n  },\n  \"stringify\": {\n    \"description\": \"Convert JavaScript objects to JSON string format. Serialize data structures into JSON strings for storage or transmission.\",\n    \"shortDescription\": \"Convert objects to JSON string\",\n    \"title\": \"Stringify JSON\"\n  },\n  \"tsvToJson\": {\n    \"description\": \"Convert TSV (Tab-Separated Values) data to JSON format. Transform tabular data into structured JSON objects.\",\n    \"shortDescription\": \"Convert TSV to JSON format\",\n    \"title\": \"TSV to JSON\"\n  },\n\n  \"jsonToCsv\": {\n    \"description\": \"Convert JSON data to CSV format. Supports nested objects and arrays, automatic header detection, and configurable delimiters and quoting — ready for Excel, Google Sheets, or any spreadsheet application.\",\n    \"shortDescription\": \"Convert JSON to CSV format\",\n    \"title\": \"JSON to CSV\",\n    \"quotingOption\": \"String Quoting\",\n    \"delimiterOption\": \"Delimiter\",\n    \"headerOption\": \"Headers\",\n    \"inputTitle\": \"Input JSON\",\n    \"outputTitle\": \"Output CSV\",\n    \"options\": {\n      \"delimiter\": \"Character used to separate values in each row (e.g. comma, semicolon, tab)\",\n      \"alwaysQuote\": {\n        \"label\": \"Always quote\",\n        \"description\": \"Wrap every cell in double-quotes, regardless of its content.\"\n      },\n      \"autoQuote\": {\n        \"label\": \"Auto (recommended)\",\n        \"description\": \"Only quote cells that contain the delimiter, a double-quote, or a newline. Keeps output clean and minimal.\"\n      },\n      \"header\": {\n        \"label\": \"Include header row\",\n        \"description\": \"Use JSON keys as column names in the first row. Recommended for readability and import compatibility.\"\n      }\n    }\n  },\n  \"validateJson\": {\n    \"description\": \"Check if JSON is valid and well-formed.\",\n    \"inputTitle\": \"Input JSON\",\n    \"invalidJson\": \"❌ {{error}}\",\n    \"resultTitle\": \"Validation Result\",\n    \"shortDescription\": \"Validate JSON code for errors\",\n    \"title\": \"Validate JSON\",\n    \"toolInfo\": {\n      \"description\": \"JSON (JavaScript Object Notation) is a lightweight data-interchange format. JSON validation ensures that the structure of the data conforms to the JSON standard. A valid JSON object must have: - Property names enclosed in double quotes. - Properly balanced curly braces {}. - No trailing commas after the last key-value pair. - Proper nesting of objects and arrays. This tool checks the input JSON and provides feedback to help identify and fix common errors.\",\n      \"title\": \"What is JSON Validation?\"\n    },\n    \"validJson\": \"✅ Valid JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/en/list.json",
    "content": "{\n  \"duplicate\": {\n    \"concatenate\": \"Concatenate\",\n    \"concatenateDescription\": \"Concatenate copies (if unchecked, items will be interweaved)\",\n    \"copyDescription\": \"Number of copies (can be fractional)\",\n    \"description\": \"World's simplest browser-based utility for duplicating list items. Input your list and specify duplication criteria to create copies of items. Perfect for data expansion, testing, or creating repeated patterns.\",\n    \"duplicationOptions\": \"Duplication Options\",\n    \"error\": \"Error\",\n    \"example1Description\": \"This example shows how to duplicate a list of words.\",\n    \"example1Title\": \"Simple duplication\",\n    \"example2Description\": \"This example shows how to duplicate a list in reverse order.\",\n    \"example2Title\": \"Reverse duplication\",\n    \"example3Description\": \"This example shows how to interweave items instead of concatenating them.\",\n    \"example3Title\": \"Interweaving items\",\n    \"example4Description\": \"This example shows how to duplicate a list with a fractional number of copies.\",\n    \"example4Title\": \"Fractional duplication\",\n    \"examples\": {\n      \"fractional\": {\n        \"description\": \"This example shows how to duplicate a list with a fractional number of copies.\",\n        \"title\": \"Fractional duplication\"\n      },\n      \"interweave\": {\n        \"description\": \"This example shows how to interweave items instead of concatenating them.\",\n        \"title\": \"Interweaving items\"\n      },\n      \"reverse\": {\n        \"description\": \"This example shows how to duplicate a list in reverse order.\",\n        \"title\": \"Reverse duplication\"\n      },\n      \"simple\": {\n        \"description\": \"This example shows how to duplicate a list of words.\",\n        \"title\": \"Simple duplication\"\n      }\n    },\n    \"inputTitle\": \"Input List\",\n    \"joinSeparatorDescription\": \"Separator to join the duplicated list\",\n    \"resultTitle\": \"Duplicated List\",\n    \"reverse\": \"Reverse\",\n    \"reverseDescription\": \"Reverse the duplicated items\",\n    \"shortDescription\": \"Duplicate list items with specified criteria\",\n    \"splitByRegex\": \"Split by Regular Expression\",\n    \"splitBySymbol\": \"Split by Symbol\",\n    \"splitOptions\": \"Split Options\",\n    \"splitSeparatorDescription\": \"Separator to split the list\",\n    \"title\": \"Duplicate\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to duplicate items in a list. You can specify the number of copies (including fractional values), control whether items are concatenated or interweaved, and even reverse the duplicated items. It's useful for creating repeated patterns, generating test data, or expanding lists with predictable content.\",\n      \"title\": \"List Duplication\"\n    },\n    \"unknownError\": \"An unknown error occurred\",\n    \"validation\": {\n      \"copyMustBeNumber\": \"Number of copies must be a number\",\n      \"copyMustBePositive\": \"Number of copies must be positive\",\n      \"copyRequired\": \"Number of copies is required\",\n      \"joinSeparatorRequired\": \"The join separator is required\",\n      \"separatorRequired\": \"The separator is required\"\n    }\n  },\n  \"findMostPopular\": {\n    \"description\": \"World's simplest browser-based utility for finding the most popular items in a list. Input your list and instantly get the items that appear most frequently. Perfect for data analysis, trend identification, or finding common elements.\",\n    \"displayFormatDescription\": \"How to display the most popular list items?\",\n    \"displayOptions\": {\n      \"count\": \"Show item count\",\n      \"percentage\": \"Show item percentage\",\n      \"total\": \"Show item total\"\n    },\n    \"extractListItems\": \"How to Extract List Items?\",\n    \"ignoreItemCase\": \"Ignore Item Case\",\n    \"ignoreItemCaseDescription\": \"Compare all list items in lowercase.\",\n    \"inputTitle\": \"Input list\",\n    \"itemComparison\": \"Item comparison\",\n    \"outputFormat\": \"Top item output format\",\n    \"removeEmptyItems\": \"Remove empty items\",\n    \"removeEmptyItemsDescription\": \"Ignore empty items from comparison.\",\n    \"resultTitle\": \"Most popular items\",\n    \"shortDescription\": \"Find most frequently occurring items\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Sort Alphabetically\",\n      \"count\": \"Sort by count\"\n    },\n    \"sortingMethodDescription\": \"Select a sorting method.\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Delimit input list items with a regular expression.\",\n        \"title\": \"Use a Regex for Splitting\"\n      },\n      \"symbol\": {\n        \"description\": \"Delimit input list items with a character.\",\n        \"title\": \"Use a Symbol for Splitting\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Set a delimiting symbol or regular expression.\",\n    \"title\": \"Find most popular\",\n    \"trimItems\": \"Trim top list items\",\n    \"trimItemsDescription\": \"Remove leading and trailing spaces before comparing items\"\n  },\n  \"findUnique\": {\n    \"caseSensitiveItems\": \"Case Sensitive Items\",\n    \"caseSensitiveItemsDescription\": \"Output items with different case as unique elements in the list.\",\n    \"delimiterDescription\": \"Set a delimiting symbol or regular expression.\",\n    \"description\": \"World's simplest browser-based utility for finding unique items in a list. Input your list and instantly get all unique values with duplicates removed. Perfect for data cleaning, deduplication, or finding distinct elements.\",\n    \"findAbsolutelyUniqueItems\": \"Find Absolutely Unique Items\",\n    \"findAbsolutelyUniqueItemsDescription\": \"Display only those items of the list that exist in a single copy.\",\n    \"inputListDelimiter\": \"Input List Delimiter\",\n    \"inputTitle\": \"Input List\",\n    \"outputListDelimiter\": \"Output List Delimiter\",\n    \"resultTitle\": \"Unique Items\",\n    \"shortDescription\": \"Find unique items in a list\",\n    \"skipEmptyItems\": \"Skip Empty Items\",\n    \"skipEmptyItemsDescription\": \"Don't include the empty list items in the output.\",\n    \"title\": \"Find unique\",\n    \"trimItems\": \"Trim List Items\",\n    \"trimItemsDescription\": \"Remove leading and trailing spaces before comparing items.\",\n    \"uniqueItemOptions\": \"Unique Item Options\"\n  },\n  \"chunk\": {\n    \"deleteEmptyItems\": \"Delete Empty Items\",\n    \"deleteEmptyItemsDescription\": \"Ignore empty items and exclude them from the result.\",\n    \"description\": \"Split a list into consecutive chunks of a fixed size. This tool separates input items, chunks them sequentially, optionally pads incomplete chunks, and formats the output using custom separators and wrappers.\",\n    \"emptyItemsAndPadding\": \"Empty Items and Padding\",\n    \"groupNumberDescription\": \"Number of items per group\",\n    \"groupSeparatorDescription\": \"Separator placed between groups\",\n    \"groupSizeAndSeparators\": \"Group Size and Output Format\",\n    \"inputItemSeparator\": \"Input Item Separator\",\n    \"inputTitle\": \"Input list\",\n    \"itemSeparatorDescription\": \"Separator placed between items inside a group\",\n    \"leftWrapDescription\": \"Symbol added before each group\",\n    \"padNonFullGroups\": \"Pad Incomplete Groups\",\n    \"padNonFullGroupsDescription\": \"Fill the last group if it has fewer items than required.\",\n    \"paddingCharDescription\": \"Character or value used to pad incomplete groups.\",\n    \"resultTitle\": \"Grouped output\",\n    \"rightWrapDescription\": \"Symbol added after each group\",\n    \"shortDescription\": \"Split a list into fixed-size chunks\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Split input items using a regular expression.\",\n        \"title\": \"Use Regex Splitting\"\n      },\n      \"symbol\": {\n        \"description\": \"Split input items using a single character.\",\n        \"title\": \"Use Symbol Splitting\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Delimiter symbol or regular expression used to split input items.\",\n    \"title\": \"Chunk List\"\n  },\n  \"reverse\": {\n    \"description\": \"This is a super simple browser-based application prints all list items in reverse. The input items can be separated by any symbol and you can also change the separator of the reversed list items.\",\n    \"inputTitle\": \"Input list\",\n    \"itemSeparator\": \"Item Separator\",\n    \"itemSeparatorDescription\": \"Set a delimiting symbol or regular expression.\",\n    \"outputListOptions\": \"Output List Options\",\n    \"outputSeparatorDescription\": \"Output list item separator.\",\n    \"resultTitle\": \"Reversed list\",\n    \"shortDescription\": \"Quickly reverse a list\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Delimit input list items with a regular expression.\",\n        \"title\": \"Use a Regex for Splitting\"\n      },\n      \"symbol\": {\n        \"description\": \"Delimit input list items with a character.\",\n        \"title\": \"Use a Symbol for Splitting\"\n      }\n    },\n    \"splitterMode\": \"Splitter Mode\",\n    \"title\": \"Reverse\",\n    \"toolInfo\": {\n      \"description\": \"With this utility, you can reverse the order of items in a list. The utility first splits the input list into individual items and then iterates through them from the last item to the first item, printing each item to the output during the iteration. The input list may contain anything that can be represented as textual data, which includes digits, numbers, strings, words, sentences, etc. The input item separator can also be a regular expression. For example, the regex /[;,]/ will allow you to use items that are either comma- or semicolon-separated. The input and output list items delimiters can be customized in the options. By default, both input and output lists are comma-separated. Listabulous!\",\n      \"title\": \"What Is a List Reverser?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"World's simplest browser-based utility for rotating list items. Input your list and specify rotation amount to shift items by a specified number of positions. Perfect for data manipulation, circular shifts, or reordering lists.\",\n    \"shortDescription\": \"Rotate list items by specified positions\",\n    \"title\": \"Rotate\"\n  },\n  \"shuffle\": {\n    \"delimiterDescription\": \"Set a delimiting symbol or regular expression.\",\n    \"description\": \"World's simplest browser-based utility for shuffling list items. Input your list and instantly get a randomized version with items in random order. Perfect for creating variety, testing randomness, or mixing up ordered data.\",\n    \"inputListSeparator\": \"Input list separator\",\n    \"inputTitle\": \"Input list\",\n    \"joinSeparatorDescription\": \"Use this separator in the randomized list.\",\n    \"outputLengthDescription\": \"Output this many random items\",\n    \"resultTitle\": \"Shuffled list\",\n    \"shortDescription\": \"Randomize the order of list items\",\n    \"shuffledListLength\": \"Shuffled List Length\",\n    \"shuffledListSeparator\": \"Shuffled List Separator\",\n    \"title\": \"Shuffle\"\n  },\n  \"sort\": {\n    \"caseSensitive\": \"Case Sensitive Sort\",\n    \"caseSensitiveDescription\": \"Sort uppercase and lowercase items separately. Capital letters precede lowercase letters in an ascending list. (Works only in alphabetical sorting mode.)\",\n    \"description\": \"World's simplest browser-based utility for sorting list items. Input your list and specify sorting criteria to organize items in ascending or descending order. Perfect for data organization, text processing, or creating ordered lists.\",\n    \"inputItemSeparator\": \"Input item separator\",\n    \"inputTitle\": \"Input list\",\n    \"joinSeparatorDescription\": \"Use this symbol as a joiner between items in a sorted list.\",\n    \"orderDescription\": \"Select a sorting order.\",\n    \"orderOptions\": {\n      \"decreasing\": \"Decreasing order\",\n      \"increasing\": \"Increasing order\"\n    },\n    \"removeDuplicates\": \"Remove duplicates\",\n    \"removeDuplicatesDescription\": \"Delete duplicate list items.\",\n    \"resultTitle\": \"Sorted list\",\n    \"shortDescription\": \"Sort list items in specified order\",\n    \"sortMethod\": \"Sort method\",\n    \"sortMethodDescription\": \"Select a sorting method.\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Sort Alphabetically\",\n      \"length\": \"Sort by Length\",\n      \"numeric\": \"Sort Numerically\"\n    },\n    \"sortedItemProperties\": \"Sorted item properties\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Delimit input list items with a regular expression.\",\n        \"title\": \"Use a Regex for Splitting\"\n      },\n      \"symbol\": {\n        \"description\": \"Delimit input list items with a character.\",\n        \"title\": \"Use a Symbol for Splitting\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Set a delimiting symbol or regular expression.\",\n    \"title\": \"Sort\"\n  },\n  \"truncate\": {\n    \"description\": \"World's simplest browser-based utility for truncating lists. Input your list and specify the maximum number of items to keep. Perfect for data processing, list management, or limiting content length.\",\n    \"shortDescription\": \"Truncate list to specified number of items\",\n    \"title\": \"Truncate\"\n  },\n  \"unwrap\": {\n    \"description\": \"World's simplest browser-based utility for unwrapping list items. Input your wrapped list and specify unwrapping criteria to flatten organized items. Perfect for data processing, text manipulation, or extracting content from structured lists.\",\n    \"shortDescription\": \"Unwrap list items from structured format\",\n    \"title\": \"Unwrap\"\n  },\n  \"wrap\": {\n    \"description\": \"Add text before and after each list item.\",\n    \"inputTitle\": \"Input List\",\n    \"joinSeparatorDescription\": \"Separator to join the wrapped list\",\n    \"leftTextDescription\": \"Text to add before each item\",\n    \"removeEmptyItems\": \"Remove empty items\",\n    \"resultTitle\": \"Wrapped List\",\n    \"rightTextDescription\": \"Text to add after each item\",\n    \"shortDescription\": \"Wrap list items with specified criteria\",\n    \"splitByRegex\": \"Split by Regular Expression\",\n    \"splitBySymbol\": \"Split by Symbol\",\n    \"splitOptions\": \"Split Options\",\n    \"splitSeparatorDescription\": \"Separator to split the list\",\n    \"title\": \"Wrap\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to add text before and after each item in a list. You can specify different text for the left and right sides, and control how the list is processed. It's useful for adding quotes, brackets, or other formatting to list items, preparing data for different formats, or creating structured text.\",\n      \"title\": \"List Wrapping\"\n    },\n    \"wrapOptions\": \"Wrap Options\"\n  }\n}\n"
  },
  {
    "path": "public/locales/en/number.json",
    "content": "{\n  \"arithmeticSequence\": {\n    \"commonDifferenceDescription\": \"Common difference between terms (d)\",\n    \"description\": \"Generate arithmetic sequences with customizable parameters.\",\n    \"firstTermDescription\": \"First term of the sequence (a₁)\",\n    \"numberOfTermsDescription\": \"Number of terms to generate (n)\",\n    \"outputFormat\": \"Output Format\",\n    \"resultTitle\": \"Generated Sequence\",\n    \"separatorDescription\": \"Separator between terms\",\n    \"sequenceParameters\": \"Sequence Parameters\",\n    \"shortDescription\": \"Generate arithmetic sequences\",\n    \"title\": \"Arithmetic Sequence\",\n    \"toolInfo\": {\n      \"description\": \"An arithmetic sequence is a sequence of numbers where the difference between each consecutive term is constant. This constant difference is called the common difference. Given the first term (a₁) and the common difference (d), each term can be found by adding the common difference to the previous term.\",\n      \"title\": \"What is an Arithmetic Sequence?\"\n    }\n  },\n  \"generate\": {\n    \"arithmeticSequenceOption\": \"Arithmetic sequence option\",\n    \"description\": \"Generate a sequence of numbers with customizable parameters.\",\n    \"numberOfElementsDescription\": \"Number of elements in sequence.\",\n    \"resultTitle\": \"Generated numbers\",\n    \"separator\": \"Separator\",\n    \"separatorDescription\": \"Separate elements in the arithmetic sequence by this character.\",\n    \"shortDescription\": \"Generate random numbers in specified ranges\",\n    \"startSequenceDescription\": \"Start sequence from this number.\",\n    \"stepDescription\": \"Increase each element by this amount\",\n    \"title\": \"Generate\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to generate a sequence of numbers with customizable parameters. You can specify the starting value, step size, and number of elements.\",\n      \"title\": \"Generate numbers\"\n    }\n  },\n  \"ohmsLaw\": {\n    \"description\": \"Calculates voltage, current and resistance\",\n    \"longDescription\": \"This calculator applies Ohm's Law (V = I × R) to determine any of the three electrical parameters when the other two are known. Ohm's Law is a fundamental principle in electrical engineering that describes the relationship between voltage (V), current (I), and resistance (R). This tool is essential for electronics hobbyists, electrical engineers, and students working with circuits to quickly solve for unknown values in their electrical designs.\",\n    \"shortDescription\": \"Calculate voltage, current, or resistance in electrical circuits using Ohm's Law\",\n    \"title\": \"Ohm's Law\"\n  },\n  \"randomNumberGenerator\": {\n    \"description\": \"Generate random numbers within a specified range with customizable options.\",\n    \"error\": {\n      \"generationFailed\": \"Failed to generate random numbers. Please check your input parameters.\"\n    },\n    \"info\": {\n      \"description\": \"A random number generator creates unpredictable numbers within a specified range. This tool uses cryptographically secure random number generation to ensure truly random results. Useful for simulations, games, statistical sampling, and testing scenarios.\",\n      \"title\": \"What is a Random Number Generator?\"\n    },\n    \"longDescription\": \"Generate random numbers within a specified range with options for integers or decimals, allowing or preventing duplicates, and sorting results. Perfect for simulations, testing, games, and statistical analysis.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDecimals\": {\n          \"description\": \"Generate decimal numbers instead of integers\",\n          \"title\": \"Allow Decimal Numbers\"\n        },\n        \"allowDuplicates\": {\n          \"description\": \"Allow the same number to appear multiple times\",\n          \"title\": \"Allow Duplicates\"\n        },\n        \"countDescription\": \"Number of random numbers to generate (1-10,000)\",\n        \"sortResults\": {\n          \"description\": \"Sort the generated numbers in ascending order\",\n          \"title\": \"Sort Results\"\n        },\n        \"title\": \"Generation Options\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Character(s) to separate the generated numbers\",\n        \"title\": \"Output Settings\"\n      },\n      \"range\": {\n        \"maxDescription\": \"Maximum value (inclusive)\",\n        \"minDescription\": \"Minimum value (inclusive)\",\n        \"title\": \"Range Settings\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Count\",\n      \"hasDuplicates\": \"Contains Duplicates\",\n      \"isSorted\": \"Sorted\",\n      \"range\": \"Range\",\n      \"title\": \"Generated Random Numbers\"\n    },\n    \"shortDescription\": \"Generate random numbers in custom ranges\",\n    \"title\": \"Random Number Generator\"\n  },\n  \"randomPortGenerator\": {\n    \"description\": \"Generate random network ports within specified ranges with customizable options.\",\n    \"error\": {\n      \"generationFailed\": \"Failed to generate random ports. Please check your input parameters.\"\n    },\n    \"info\": {\n      \"description\": \"A random port generator creates unpredictable network port numbers within specified ranges. This tool follows IANA port number standards and includes identification of common services. Useful for development, testing, network configuration, and avoiding port conflicts.\",\n      \"title\": \"What is a Random Port Generator?\"\n    },\n    \"longDescription\": \"Generate random network ports within specified ranges (well-known, registered, dynamic, or custom). Perfect for development, testing, and network configuration. Includes port service identification for common ports.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDuplicates\": {\n          \"description\": \"Allow the same port to appear multiple times\",\n          \"title\": \"Allow Duplicates\"\n        },\n        \"countDescription\": \"Number of random ports to generate (1-1,000)\",\n        \"sortResults\": {\n          \"description\": \"Sort the generated ports in ascending order\",\n          \"title\": \"Sort Results\"\n        },\n        \"title\": \"Generation Options\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Character(s) to separate the generated ports\",\n        \"title\": \"Output Settings\"\n      },\n      \"range\": {\n        \"custom\": \"Custom Range\",\n        \"dynamic\": \"Dynamic Ports (49152-65535)\",\n        \"maxPortDescription\": \"Maximum port number (1-65535)\",\n        \"minPortDescription\": \"Minimum port number (1-65535)\",\n        \"registered\": \"Registered Ports (1024-49151)\",\n        \"title\": \"Port Range Settings\",\n        \"wellKnown\": \"Well-Known Ports (1-1023)\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Count\",\n      \"hasDuplicates\": \"Contains Duplicates\",\n      \"isSorted\": \"Sorted\",\n      \"portDetails\": \"Port Details\",\n      \"range\": \"Port Range\",\n      \"title\": \"Generated Random Ports\"\n    },\n    \"shortDescription\": \"Generate random network ports\",\n    \"title\": \"Random Port Generator\"\n  },\n  \"slackline\": {\n    \"description\": \"Calculates tension in a slackline\",\n    \"longDescription\": \"This calculator assumes a load in the center of the rope\",\n    \"shortDescription\": \"Calculate the approximate tension of a slackline or clothesline. Do not rely on this for safety.\",\n    \"title\": \"Slackline Tension\"\n  },\n  \"sphereArea\": {\n    \"description\": \"Area of a Sphere\",\n    \"longDescription\": \"This calculator determines the surface area of a sphere using the formula A = 4πr². You can either input the radius to find the surface area or enter the surface area to calculate the required radius. This tool is useful for students studying geometry, engineers working with spherical objects, and anyone needing to perform calculations involving spherical surfaces.\",\n    \"shortDescription\": \"Calculate the surface area of a sphere based on its radius\",\n    \"title\": \"Area of a Sphere\"\n  },\n  \"sphereVolume\": {\n    \"description\": \"Volume of a Sphere\",\n    \"longDescription\": \"This calculator computes the volume of a sphere using the formula V = (4/3)πr³. You can input either the radius or diameter to find the volume, or enter the volume to determine the required radius. The tool is valuable for students, engineers, and professionals working with spherical objects in fields such as physics, engineering, and manufacturing.\",\n    \"shortDescription\": \"Calculate the volume of a sphere using radius or diameter\",\n    \"title\": \"Volume of a Sphere\"\n  },\n  \"sum\": {\n    \"description\": \"Calculate the sum of a list of numbers. Enter numbers separated by commas or newlines to get their total sum.\",\n    \"example1Description\": \"In this example, we calculate the sum of ten positive integers. These integers are listed as a column and their total sum equals 19494.\",\n    \"example1Title\": \"Sum of Ten Positive Numbers\",\n    \"example2Description\": \"This example reverses a column of twenty three-syllable nouns and prints all the words from the bottom to top. To separate the list items, it uses the \\\\n character as input item separator, which means that each item is on its own line.\",\n    \"example2Title\": \"Count Trees in the Park\",\n    \"example3Description\": \"In this example, we add together ninety different values – positive numbers, negative numbers, integers and decimal fractions. We set the input separator to a comma and after adding all of them together, we get 0 as output.\",\n    \"example3Title\": \"Sum of Integers and Decimals\",\n    \"example4Description\": \"In this example, we calculate the sum of all ten digits and enable the option \\\"Print Running Sum\\\". We get the intermediate values of the sum in the process of addition. Thus, we have the following sequence in the output: 0, 1 (0 + 1), 3 (0 + 1 + 2), 6 (0 + 1 + 2 + 3), 10 (0 + 1 + 2 + 3 + 4), and so on.\",\n    \"example4Title\": \"Running Sum of Numbers\",\n    \"extractionTypes\": {\n      \"delimiter\": {\n        \"description\": \"Customize the number separator here. (By default a line break.)\",\n        \"title\": \"Number Delimiter\"\n      },\n      \"smart\": {\n        \"description\": \"Auto detect numbers in the input.\",\n        \"title\": \"Smart Sum\"\n      }\n    },\n    \"inputTitle\": \"Input\",\n    \"numberExtraction\": \"Number Extraction\",\n    \"printRunningSum\": \"Print Running Sum\",\n    \"printRunningSumDescription\": \"Display the sum as it's calculated step by step.\",\n    \"resultTitle\": \"Total\",\n    \"runningSum\": \"Running Sum\",\n    \"shortDescription\": \"Calculate sum of numbers\",\n    \"title\": \"Sum\",\n    \"toolInfo\": {\n      \"description\": \"This is an online browser-based utility for calculating the sum of a bunch of numbers. You can enter the numbers separated by a comma, space, or any other character, including the line break. You can also simply paste a fragment of textual data that contains numerical values that you want to sum up and the utility will extract them and find their sum.\",\n      \"title\": \"What Is a Number Sum Calculator?\"\n    }\n  },\n  \"voltageDropInWire\": {\n    \"description\": \"Calculates round trip voltage and power loss in a 2 conductor cable\",\n    \"longDescription\": \"This calculator helps determine the voltage drop and power loss in a two-conductor electrical cable. It takes into account the cable length, wire gauge (cross-sectional area), material resistivity, and current flow. The tool calculates the round-trip voltage drop, total resistance of the cable, and the power dissipated as heat. This is particularly useful for electrical engineers, electricians, and hobbyists when designing electrical systems to ensure voltage levels remain within acceptable limits at the load.\",\n    \"shortDescription\": \"Calculate voltage drop and power loss in electrical cables based on length, material, and current\",\n    \"title\": \"Round trip voltage drop in cable\"\n  },\n  \"byteConverter\": {\n    \"description\": \"Convert data values between different byte units, supporting both SI (decimal) and IEC (binary) systems. It supports multiple lines conversion.\",\n    \"title\": \"Byte Converter\",\n    \"shortDescription\": \"Quickly convert values between byte units\",\n    \"inputTitle\": \"Input Values\",\n    \"outputTitle\": \"Converted Output\",\n    \"unit\": {\n      \"title\": \"Conversion Options\",\n      \"from\": \"Select the unit of the input values you are providing (e.g., KB, MB, GiB).\",\n      \"to\": \"Select the unit you want the input values to be converted into.\",\n      \"precision\": \"Number of decimal places to display in the converted output.\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/en/pdf.json",
    "content": "{\n  \"compressPdf\": {\n    \"compressedFileSize\": \"Compressed File Size\",\n    \"compressingPdf\": \"Compressing PDF...\",\n    \"compressionLevel\": \"Compression Level\",\n    \"compressionSettings\": \"Compression Settings\",\n    \"description\": \"Reduce PDF file size while maintaining quality using Ghostscript\",\n    \"errorCompressingPdf\": \"Failed to compress PDF: {{error}}\",\n    \"errorReadingPdf\": \"Failed to read PDF file. Please make sure it is a valid PDF.\",\n    \"fileSize\": \"Original File Size\",\n    \"highCompression\": \"High Compression\",\n    \"highCompressionDescription\": \"Maximum file size reduction with some quality loss\",\n    \"inputTitle\": \"Input PDF\",\n    \"longDescription\": \"Compress PDF files securely in your browser using Ghostscript. Your files never leave your device, ensuring complete privacy while reducing file sizes for email sharing, uploading to websites, or saving storage space. Powered by WebAssembly technology.\",\n    \"lowCompression\": \"Low Compression\",\n    \"lowCompressionDescription\": \"Slightly reduce file size with minimal quality loss\",\n    \"mediumCompression\": \"Medium Compression\",\n    \"mediumCompressionDescription\": \"Balance between file size and quality\",\n    \"pages\": \"Number of Pages\",\n    \"resultTitle\": \"Compressed PDF\",\n    \"shortDescription\": \"Compress PDF files securely in your browser\",\n    \"title\": \"Compress PDF\"\n  },\n  \"editor\": {\n    \"description\": \"Advanced PDF editor with annotation, form-fill, highlight, and export capabilities. Edit your PDFs directly in the browser with professional-grade tools including text insertion, drawing, highlighting, signing and form filling.\",\n    \"shortDescription\": \"Edit PDFs with advanced annotation, signing and editing tools\",\n    \"title\": \"PDF Editor\"\n  },\n  \"merge\": {\n    \"inputTitle\": \"Input PDF\",\n    \"loadingText\": \"Extracting pages\",\n    \"resultTitle\": \"Output merged PDF\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to merge multiple PDF files into a single document. To use the tool, simply upload the PDF files you want to merge. The tool will then combine all pages from the input files into a single PDF document.\",\n      \"title\": \"How to Use the Merge PDF Tool?\"\n    }\n  },\n  \"mergePdf\": {\n    \"description\": \"Combine multiple PDF files into a single document.\",\n    \"inputTitle\": \"Input PDFs\",\n    \"mergingPdfs\": \"Merging PDFs\",\n    \"pdfOptions\": \"PDF Options\",\n    \"resultTitle\": \"Merged PDF\",\n    \"shortDescription\": \"Merge multiple PDF files into a single document\",\n    \"sortByFileName\": \"Sort by file name\",\n    \"sortByFileNameDescription\": \"Sort PDFs alphabetically by file name\",\n    \"sortByUploadOrder\": \"Sort by upload order\",\n    \"sortByUploadOrderDescription\": \"Keep PDFs in the order they were uploaded\",\n    \"title\": \"Merge PDF\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to combine multiple PDF files into a single document. You can choose how to sort the PDFs and the tool will merge them in the specified order.\",\n      \"title\": \"Merge PDF Files\"\n    }\n  },\n  \"pdfToEpub\": {\n    \"description\": \"Transform PDF documents into EPUB files for better e-reader compatibility.\",\n    \"shortDescription\": \"Convert PDF files to EPUB format\",\n    \"title\": \"PDF to EPUB\"\n  },\n  \"pdfToPng\": {\n    \"description\": \"Transform PDF documents into PNG panels.\",\n    \"longDescription\": \"Upload a PDF and convert each page into a high-quality PNG image directly in your browser. This tool is ideal for extracting visual content or sharing individual pages. No data is uploaded — everything runs locally.\",\n    \"shortDescription\": \"Convert PDF into PNG images\",\n    \"title\": \"PDF to PNG\"\n  },\n  \"convertToPdf\": {\n    \"title\": \"Images to PDF\",\n    \"description\": \"Convert various image formats (PNG, GIF, JPG, TIF, PSD, SVG, WEBP, HEIC, RAW) to PDF, with options to scale the image and choose page orientation.\",\n    \"shortDescription\": \"Convert images to PDF with scale and orientation control\"\n  },\n  \"protectPdf\": {\n    \"description\": \"Add password protection to your PDF files securely in your browser\",\n    \"shortDescription\": \"Password protect PDF files securely\",\n    \"title\": \"Protect PDF\"\n  },\n  \"rotatePdf\": {\n    \"allPagesWillBeRotated\": \"All {{count}} pages will be rotated\",\n    \"angleOptions\": {\n      \"clockwise90\": \"90° Clockwise\",\n      \"counterClockwise270\": \"270° (90° Counter-clockwise)\",\n      \"upsideDown180\": \"180° (Upside down)\"\n    },\n    \"applyToAllPages\": \"Apply to all pages\",\n    \"description\": \"Rotate pages in a PDF document.\",\n    \"inputTitle\": \"Input PDF\",\n    \"longDescription\": \"Change the orientation of PDF pages by rotating them 90, 180, or 270 degrees. Useful for fixing incorrectly scanned documents or preparing PDFs for printing.\",\n    \"pageRangesDescription\": \"Enter page numbers or ranges separated by commas (e.g., 1,3,5-7)\",\n    \"pageRangesPlaceholder\": \"e.g., 1,5-8\",\n    \"pagesWillBeRotated\": \"{{count}} page{{count !== 1 ? 's' : ''}} will be rotated\",\n    \"pdfPageCount\": \"PDF has {{count}} page{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"Rotated PDF\",\n    \"rotatingPages\": \"Rotating pages\",\n    \"rotationAngle\": \"Rotation Angle\",\n    \"rotationSettings\": \"Rotation Settings\",\n    \"shortDescription\": \"Rotate pages in a PDF document\",\n    \"title\": \"Rotate PDF\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to rotate pages in a PDF document. You can rotate all pages or specify individual pages to rotate. Choose a rotation angle: 90° Clockwise, 180° (Upside down), or 270° (90° Counter-clockwise). To rotate specific pages, uncheck \\\"Apply to all pages\\\" and enter page numbers or ranges separated by commas (e.g., 1,3,5-7).\",\n      \"title\": \"How to Use the Rotate PDF Tool\"\n    }\n  },\n  \"splitPdf\": {\n    \"description\": \"Extract specific pages from a PDF document.\",\n    \"extractingPages\": \"Extracting pages\",\n    \"inputTitle\": \"Input PDF\",\n    \"pageExtractionPreview\": \"{{count}} page{{count !== 1 ? 's' : ''}} will be extracted\",\n    \"pageRangesDescription\": \"Enter page numbers or ranges separated by commas (e.g., 1,3,5-7)\",\n    \"pageRangesPlaceholder\": \"e.g., 1,5-8\",\n    \"pageSelection\": \"Page Selection\",\n    \"pdfPageCount\": \"PDF has {{count}} page{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"Extracted PDF\",\n    \"shortDescription\": \"Extract specific pages from a PDF file\",\n    \"title\": \"Split PDF\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to extract specific pages from a PDF document. You can specify individual pages or ranges of pages to extract.\",\n      \"title\": \"Split PDF\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/en/string.json",
    "content": "{\n  \"base64\": {\n    \"decode\": \"Base64 Decode\",\n    \"description\": \"Encode or decode text using Base64 encoding.\",\n    \"encode\": \"Base64 Encode\",\n    \"inputTitle\": \"Input Data\",\n    \"optionsTitle\": \"Base64 Options\",\n    \"resultTitle\": \"Result\",\n    \"shortDescription\": \"Encode or decode data using Base64.\",\n    \"title\": \"Base64 Encoder/Decoder\",\n    \"toolInfo\": {\n      \"description\": \"Base64 is an encoding scheme that represents data in an ASCII string format by translating it into a radix-64 representation. Although it can be used to encode strings, it is commonly used to encode binary data for transmission over media that are designed to deal with textual data.\",\n      \"title\": \"What is Base64?\"\n    }\n  },\n  \"censor\": {\n    \"description\": \"utility for censoring words in text. Load your text in the input form on the left, specify all the bad words in the options, and you'll instantly get censored text in the output area.\\\", longDescription: 'With this online tool, you can censor certain words in any text. You can specify a list of unwanted words (such as swear words or secret words) and the program will replace them with alternative words and create a safe-to-read text. The words can be specified in a multi-line text field in the options by entering one word per line.', keywords: ['text', 'censor', 'words', 'characters'], component: lazy(() => import('./index')), i18n: { name: 'string:censor.title', description: 'string:censor.description\",\n    \"shortDescription\": \"Quickly mask bad words or replace them with alternative words.\",\n    \"title\": \"Text Censor\"\n  },\n  \"createPalindrome\": {\n    \"description\": \"World's simplest browser-based utility for creating palindromes from any text. Input text and instantly transform it into a palindrome that reads the same forward and backward. Perfect for word games, creating symmetrical text patterns, or exploring linguistic curiosities.\",\n    \"shortDescription\": \"Create text that reads the same forward and backward\",\n    \"title\": \"Create palindrome\"\n  },\n  \"extractSubstring\": {\n    \"description\": \"World's simplest browser-based utility for extracting substrings from text. Input your text and specify start and end positions to extract the desired portion. Perfect for data processing, text analysis, or extracting specific content from larger text blocks.\",\n    \"shortDescription\": \"Extract a portion of text between specified positions\",\n    \"title\": \"Extract substring\"\n  },\n  \"hiddenCharacterDetector\": {\n    \"analysisOptions\": \"Analysis Options\",\n    \"category\": \"Category\",\n    \"description\": \"Detect hidden Unicode characters, especially RTL Override characters that could be used in attacks.\",\n    \"foundChars\": \"Found {{count}} hidden character(s):\",\n    \"inputPlaceholder\": \"Enter text to check for hidden characters...\",\n    \"inputTitle\": \"Text to Analyze\",\n    \"invisibleChar\": \"Invisible Character\",\n    \"invisibleFound\": \"Invisible characters found\",\n    \"longDescription\": \"This tool helps you detect hidden Unicode characters in text, particularly Right-to-Left (RTL) Override characters that can be used in attacks. It can identify invisible characters, zero-width characters, and other potentially malicious Unicode sequences that might be hidden in seemingly innocent text.\",\n    \"noHiddenChars\": \"No hidden characters detected in the text.\",\n    \"optionsDescription\": \"Configure which types of hidden characters to detect and how to display the results.\",\n    \"position\": \"Position\",\n    \"rtlAlert\": \"⚠️ RTL Override characters detected! This text may contain malicious hidden characters.\",\n    \"rtlFound\": \"RTL Override found\",\n    \"rtlOverride\": \"RTL Override Character\",\n    \"rtlWarning\": \"WARNING: RTL Override characters detected! This could be used in attacks.\",\n    \"shortDescription\": \"Find hidden Unicode characters in text\",\n    \"summary\": \"Analysis Summary\",\n    \"title\": \"Hidden Character Detector\",\n    \"totalChars\": \"Total hidden characters: {{count}}\",\n    \"unicode\": \"Unicode\",\n    \"zeroWidthChar\": \"Zero Width Character\",\n    \"zeroWidthFound\": \"Zero-width characters found\"\n  },\n  \"join\": {\n    \"blankLinesAndTrailingSpaces\": \"Blank Lines and Trailing Spaces\",\n    \"deleteBlankDescription\": \"Delete lines that don't have text symbols.\",\n    \"deleteBlankTitle\": \"Delete Blank Lines\",\n    \"deleteTrailingDescription\": \"Remove spaces and tabs at the end of the lines.\",\n    \"deleteTrailingTitle\": \"Delete Trailing Spaces\",\n    \"description\": \"Join text pieces together with customizable separators.\",\n    \"inputTitle\": \"Text Pieces\",\n    \"joinCharacterDescription\": \"Symbol that connects broken pieces of text. (Space by default.)\",\n    \"joinCharacterPlaceholder\": \"Join Character\",\n    \"resultTitle\": \"Joined Text\",\n    \"shortDescription\": \"Join text elements with a specified separator\",\n    \"textMergedOptions\": \"Text Merged Options\",\n    \"title\": \"Join Text\",\n    \"toolInfo\": {\n      \"description\": \"With this tool you can join parts of the text together. It takes a list of text values, separated by newlines, and merges them together. You can set the character that will be placed between the parts of the combined text. Also, you can ignore all empty lines and remove spaces and tabs at the end of all lines. Textabulous!\",\n      \"title\": \"What Is a Text Joiner?\"\n    }\n  },\n  \"palindrome\": {\n    \"description\": \"World's simplest browser-based utility for checking if text is a palindrome. Instantly verify if your text reads the same forward and backward. Perfect for word puzzles, linguistic analysis, or validating symmetrical text patterns. Supports various delimiters and multi-word palindrome detection.\",\n    \"shortDescription\": \"Check if text reads the same forward and backward\",\n    \"title\": \"Palindrome\"\n  },\n  \"passwordGenerator\": {\n    \"avoidAmbiguous\": \"Avoid ambiguous characters (i, I, l, 0, O)\",\n    \"description\": \"Generate secure random passwords with customizable length and character types. Choose from lowercase, uppercase, numbers, and special characters. Option to avoid ambiguous characters for better readability.\",\n    \"includeLowercase\": \"Include lowercase letters (a-z)\",\n    \"includeNumbers\": \"Include numbers (0-9)\",\n    \"includeSymbols\": \"Include special characters\",\n    \"includeUppercase\": \"Include uppercase letters (A-Z)\",\n    \"lengthDesc\": \"Length of the password\",\n    \"lengthPlaceholder\": \"e.g. 12\",\n    \"optionsTitle\": \"Password Options\",\n    \"resultTitle\": \"Generated Password\",\n    \"shortDescription\": \"Generate secure random passwords with custom options\",\n    \"title\": \"Password Generator\",\n    \"toolInfo\": {\n      \"description\": \"This tool generates secure random passwords based on your selected criteria. You can customize the length, include or exclude different character types, and avoid ambiguous characters for better readability. Perfect for creating strong passwords for accounts, applications, or any security needs.\",\n      \"title\": \"About Password Generator\"\n    }\n  },\n  \"quote\": {\n    \"allowDoubleQuotation\": \"Allow double quotation\",\n    \"description\": \"Add quotes around text with customizable options.\",\n    \"inputTitle\": \"Input Text\",\n    \"leftQuoteDescription\": \"Left quote character(s)\",\n    \"processAsMultiLine\": \"Process as multi-line text\",\n    \"quoteEmptyLines\": \"Quote empty lines\",\n    \"quoteOptions\": \"Quote Options\",\n    \"resultTitle\": \"Quoted Text\",\n    \"rightQuoteDescription\": \"Right quote character(s)\",\n    \"shortDescription\": \"Add quotes around text with various styles\",\n    \"title\": \"Text Quoter\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to add quotes around text. You can choose different quote characters, handle multi-line text, and control how empty lines are processed. It's useful for preparing text for programming, formatting data, or creating stylized text.\",\n      \"title\": \"Text Quoter\"\n    }\n  },\n  \"randomizeCase\": {\n    \"description\": \"World's simplest browser-based utility for randomizing text case. Input your text and instantly transform it with random upper and lower case letters. Perfect for creating unique text effects, testing case sensitivity, or generating varied text patterns.\",\n    \"shortDescription\": \"Randomize the case of letters in text\",\n    \"title\": \"Randomize case\"\n  },\n  \"removeDuplicateLines\": {\n    \"description\": \"Load your text in the input form on the left and you'll instantly get text with no duplicate lines in the output area. Powerful, free, and fast. Load text lines – get unique text lines\",\n    \"shortDescription\": \"Quickly delete all repeated lines from text\",\n    \"title\": \"Remove duplicate lines\"\n  },\n  \"repeat\": {\n    \"delimiterDescription\": \"Delimiter for output copies.\",\n    \"delimiterPlaceholder\": \"Delimiter\",\n    \"description\": \"Repeat text multiple times with customizable separators.\",\n    \"inputTitle\": \"Input text\",\n    \"numberPlaceholder\": \"Number\",\n    \"repeatAmountDescription\": \"Number of repetitions.\",\n    \"repetitionsDelimiter\": \"Repetitions Delimiter\",\n    \"resultTitle\": \"Repeated text\",\n    \"shortDescription\": \"Repeat text multiple times\",\n    \"textRepetitions\": \"Text Repetitions\",\n    \"title\": \"Repeat Text\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to repeat a given text multiple times with an optional separator.\",\n      \"title\": \"Repeat text\"\n    }\n  },\n  \"reverse\": {\n    \"description\": \"World's simplest browser-based utility for reversing text. Input any text and get it instantly reversed, character by character. Perfect for creating mirror text, analyzing palindromes, or playing with text patterns. Preserves spaces and special characters while reversing.\",\n    \"inputTitle\": \"Text to reverse\",\n    \"processMultiLine\": \"Process multi-line text\",\n    \"processMultiLineDescription\": \"Each line will be reversed independently\",\n    \"resultTitle\": \"Reversed text\",\n    \"reversalOptions\": \"Reversal options\",\n    \"shortDescription\": \"Reverse any text character by character\",\n    \"skipEmptyLines\": \"Skip empty lines\",\n    \"skipEmptyLinesDescription\": \"Empty lines will be removed from the output\",\n    \"title\": \"Reverse\",\n    \"trimWhitespace\": \"Trim whitespace\",\n    \"trimWhitespaceDescription\": \"Remove leading and trailing whitespace from each line\"\n  },\n  \"rot13\": {\n    \"description\": \"Encode or decode text using ROT13 cipher.\",\n    \"inputTitle\": \"Input Text\",\n    \"resultTitle\": \"ROT13 Result\",\n    \"shortDescription\": \"Encode or decode text using ROT13 cipher.\",\n    \"title\": \"ROT13 Encoder/Decoder\",\n    \"toolInfo\": {\n      \"description\": \"ROT13 (rotate by 13 places) is a simple letter substitution cipher that replaces a letter with the 13th letter after it in the alphabet. ROT13 is a special case of the Caesar cipher which was developed in ancient Rome. Because there are 26 letters in the English alphabet, ROT13 is its own inverse; that is, to undo ROT13, the same algorithm is applied, so the same action can be used for encoding and decoding.\",\n      \"title\": \"What Is ROT13?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"Rotate characters in text by specified positions.\",\n    \"inputTitle\": \"Input Text\",\n    \"processAsMultiLine\": \"Process as multi-line text (rotate each line separately)\",\n    \"resultTitle\": \"Rotated Text\",\n    \"rotateLeft\": \"Rotate Left\",\n    \"rotateRight\": \"Rotate Right\",\n    \"rotationOptions\": \"Rotation Options\",\n    \"shortDescription\": \"Shift characters in text by position.\",\n    \"stepDescription\": \"Number of positions to rotate\",\n    \"title\": \"Rotate Text\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to rotate characters in a string by a specified number of positions. You can rotate to the left or right, and process multi-line text by rotating each line separately. String rotation is useful for simple text transformations, creating patterns, or implementing basic encryption techniques.\",\n      \"title\": \"String Rotation\"\n    }\n  },\n  \"split\": {\n    \"charAfterChunkDescription\": \"Character after each chunk\",\n    \"charBeforeChunkDescription\": \"Character before each chunk\",\n    \"chunksDescription\": \"Number of chunks of equal length in the output.\",\n    \"chunksTitle\": \"Use a Number of Chunks\",\n    \"description\": \"World's simplest browser-based utility for splitting text. Input your text and specify a separator to split it into multiple parts. Perfect for data processing, text manipulation, or extracting specific content from larger text blocks.\",\n    \"lengthDescription\": \"Number of characters that will be put in each output chunk.\",\n    \"lengthTitle\": \"Use Length for Splitting\",\n    \"outputSeparatorDescription\": \"Character that will be put between the split chunks. (It's newline \\\"\\\\n\\\" by default.)\",\n    \"outputSeparatorOptions\": \"Output Separator Options\",\n    \"regexDescription\": \"Regular expression that will be used to break text into parts. (Multiple spaces by default.)\",\n    \"regexTitle\": \"Use a Regex for Splitting\",\n    \"resultTitle\": \"Split Result\",\n    \"shortDescription\": \"Split text into multiple parts using a separator\",\n    \"splitSeparatorOptions\": \"Split separator options\",\n    \"symbolDescription\": \"Character that will be used to break text into parts. (Space by default.)\",\n    \"symbolTitle\": \"Use a Symbol for Splitting\",\n    \"title\": \"Split\"\n  },\n  \"statistic\": {\n    \"characterFrequencyAnalysis\": \"Character Frequency Analysis\",\n    \"characterFrequencyAnalysisDescription\": \"Count how often each character appears in the text\",\n    \"delimitersOptions\": \"Delimiters Options\",\n    \"description\": \"Analyze text and generate comprehensive statistics.\",\n    \"includeEmptyLines\": \"Include Empty Lines\",\n    \"includeEmptyLinesDescription\": \"Include blank lines when counting lines\",\n    \"inputTitle\": \"Input text\",\n    \"resultTitle\": \"Text Statistics\",\n    \"sentenceDelimitersDescription\": \"Enter custom characters used to delimit sentences in your language (separated by comma) or leave it blank for default.\",\n    \"sentenceDelimitersPlaceholder\": \"e.g. ., !, ?, ...\",\n    \"shortDescription\": \"Get statistics about your text\",\n    \"statisticsOptions\": \"Statistics Options\",\n    \"title\": \"Text Statistics\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to analyze text and generate comprehensive statistics including character count, word count, line count, and frequency analysis of characters and words.\",\n      \"title\": \"What is a {{title}}?\"\n    },\n    \"wordDelimitersDescription\": \"Enter custom Regex to count Words or leave it blank for default.\",\n    \"wordDelimitersPlaceholder\": \"eg. \\\\s.,;:!?\\\"«»()…\",\n    \"wordFrequencyAnalysis\": \"Word Frequency Analysis\",\n    \"wordFrequencyAnalysisDescription\": \"Count how often each word appears in the text\"\n  },\n  \"textReplacer\": {\n    \"description\": \"Replace text patterns with new content.\",\n    \"findPatternInText\": \"Find This Pattern in Text\",\n    \"findPatternUsingRegexp\": \"Find a Pattern Using a RegExp\",\n    \"inputTitle\": \"Text to replace\",\n    \"newTextPlaceholder\": \"New text\",\n    \"regexpDescription\": \"Enter the regular expression that you want to replace.\",\n    \"replacePatternDescription\": \"Enter the pattern to use for replacement.\",\n    \"replaceText\": \"Replace Text\",\n    \"resultTitle\": \"Text with replacements\",\n    \"searchPatternDescription\": \"Enter the text pattern that you want to replace.\",\n    \"searchText\": \"Search text\",\n    \"shortDescription\": \"Quickly replace text in your content\",\n    \"title\": \"Text Replacer\",\n    \"toolInfo\": {\n      \"description\": \"Easily replace specific text in your content with this simple, browser-based tool. Just input your text, set the text you want to replace and the replacement value, and instantly get the updated version.\",\n      \"title\": \"Text Replacer\"\n    }\n  },\n  \"toMorse\": {\n    \"dashSymbolDescription\": \"Symbol that will correspond to the dash in Morse code.\",\n    \"description\": \"Convert text to Morse code.\",\n    \"dotSymbolDescription\": \"Symbol that will correspond to the dot in Morse code.\",\n    \"longSignal\": \"Long Signal\",\n    \"resultTitle\": \"Morse code\",\n    \"shortDescription\": \"Quickly encode text to morse\",\n    \"shortSignal\": \"Short Signal\",\n    \"title\": \"String To morse\"\n  },\n  \"truncate\": {\n    \"addTruncationIndicator\": \"Add Truncation Indicator\",\n    \"charactersPlaceholder\": \"Characters\",\n    \"description\": \"Shorten text to a specified length.\",\n    \"indicatorDescription\": \"Characters to add at the end (or start) of the text. Note: They count towards the length.\",\n    \"inputTitle\": \"Input text\",\n    \"leftSideDescription\": \"Remove characters from the start of the text.\",\n    \"leftSideTruncation\": \"Left-side Truncation\",\n    \"lengthAndLines\": \"Length and Lines\",\n    \"lineByLineDescription\": \"Truncate each line separately.\",\n    \"lineByLineTruncating\": \"Line-by-line Truncating\",\n    \"maxLengthDescription\": \"Number of characters to leave in the text.\",\n    \"numberPlaceholder\": \"Number\",\n    \"resultTitle\": \"Truncated text\",\n    \"rightSideDescription\": \"Remove characters from the end of the text.\",\n    \"rightSideTruncation\": \"Right-side Truncation\",\n    \"shortDescription\": \"Truncate text to a specified length\",\n    \"suffixAndAffix\": \"Suffix and Affix\",\n    \"title\": \"Truncate Text\",\n    \"toolInfo\": {\n      \"description\": \"Load your text in the input form on the left and you will automatically get truncated text on the right.\",\n      \"title\": \"Truncate text\"\n    },\n    \"truncationSide\": \"Truncation Side\"\n  },\n  \"uppercase\": {\n    \"description\": \"Convert text to uppercase letters.\",\n    \"inputTitle\": \"Input text\",\n    \"resultTitle\": \"Uppercase text\",\n    \"shortDescription\": \"Convert text to uppercase\",\n    \"title\": \"Convert to Uppercase\"\n  },\n  \"urlDecode\": {\n    \"inputTitle\": \"Input String(URL-escaped)\",\n    \"resultTitle\": \"Output string\",\n    \"toolInfo\": {\n      \"description\": \"Load your string and it will automatically get URL-unescaped.\",\n      \"longDescription\": \"This tool URL-decodes a previously URL-encoded string. URL-decoding is the inverse operation of URL-encoding. All percent-encoded characters get decoded to characters that you can understand. Some of the most well known percent-encoded values are %20 for a space, %3a for a colon, %2f for a slash, and %3f for a question mark. The two digits following the percent sign are character's char code values in hex.\",\n      \"shortDescription\": \"Quickly URL-unescape a string.\",\n      \"title\": \"String URL decoder\"\n    }\n  },\n  \"urlEncode\": {\n    \"encodingOption\": {\n      \"nonSpecialCharDescription\": \"If selected, then all characters in the input string will be converted to URL-encoding (not just special).\",\n      \"nonSpecialCharPlaceholder\": \"Encode non-special characters\",\n      \"title\": \"Encoding Options\"\n    },\n    \"inputTitle\": \"Input String\",\n    \"resultTitle\": \"Url-escaped String\",\n    \"toolInfo\": {\n      \"description\": \"Load your string and it will automatically get URL-escaped.\",\n      \"longDescription\": \"This tool URL-encodes a string. Special URL characters get converted to percent-sign encoding. This encoding is called percent-encoding because each character's numeric value gets converted to a percent sign followed by a two-digit hexadecimal value. The hex values are determined based on the character's codepoint value. For example, a space gets escaped to %20, a colon to %3a, a slash to %2f. Characters that are not special stay unchanged. In case you also need to convert non-special characters to percent-encoding, then we've also added an extra option that lets you do that. Select the encode-non-special-chars option to enable this behavior.\",\n      \"shortDescription\": \"Quickly URL-escape a string.\",\n      \"title\": \"String URL encoder\"\n    }\n  },\n  \"unicode\": {\n    \"title\": \"Unicode Encoder / Decoder\",\n    \"inputTitle\": \"Input\",\n    \"resultTitle\": \"Processed Output\",\n    \"optionsTitle\": \"Mode\",\n    \"caseOptionsTitle\": \"Case Options\",\n    \"encode\": \"Encode\",\n    \"decode\": \"Decode\",\n    \"uppercase\": \"Uppercase Hex\",\n    \"description\": \"Convert text to Unicode escape sequences or decode them back to readable text.\",\n    \"shortDescription\": \"Encode or decode text using Unicode escape sequences.\",\n    \"toolInfo\": {\n      \"title\": \"Unicode Encoder / Decoder\",\n      \"description\": \"This tool lets you convert plain text into Unicode escape sequences (e.g., \\\\uXXXX) and decode Unicode escape sequences back into standard text. You can also choose whether the hexadecimal output is formatted in uppercase or lowercase when encoding.\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/en/time.json",
    "content": "{\n  \"checkLeapYears\": {\n    \"description\": \"Check if a year is a leap year and get leap year information.\",\n    \"exampleDescription\": \"One of our friends was born on a leap year on February 29th and as a consequence, she has a birthday only once every 4 years. As we can never really remember when her birthday is, we are using our program to create a reminder list of the upcoming leap years. To create a list of her next birthdays, we load a sequence of years from 2025 to 2040 into the input and get the status of each year. If the program says that it's a leap year, then we know that we'll be invited to a birthday party on February 29th.\",\n    \"exampleTitle\": \"Find Birthdays on February 29\",\n    \"inputTitle\": \"Input year\",\n    \"resultTitle\": \"Leap year result\",\n    \"shortDescription\": \"Check if a year is a leap year\",\n    \"title\": \"Check Leap Years\",\n    \"toolInfo\": {\n      \"description\": \"A leap year is a year containing one additional day (February 29) to keep the calendar year synchronized with the astronomical year. Leap years occur every 4 years, except for years that are divisible by 100 but not by 400.\",\n      \"title\": \"What is a Leap Year?\"\n    }\n  },\n  \"convertDaysToHours\": {\n    \"addHoursName\": \"Add Hours Name\",\n    \"addHoursNameDescription\": \"Append the string hours to output values\",\n    \"description\": \"Convert days to hours with customizable options.\",\n    \"hoursName\": \"Hours Name\",\n    \"shortDescription\": \"Convert days to hours\",\n    \"title\": \"Convert Days to Hours\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to convert days to hours. You can input days as numbers or with units, and the tool will convert them to hours. You can also choose to append the 'hours' suffix to the output values.\",\n      \"title\": \"Convert Days to Hours\"\n    }\n  },\n  \"convertHoursToDays\": {\n    \"addDaysName\": \"Add Days Name\",\n    \"addDaysNameDescription\": \"Append the string days to output values\",\n    \"daysName\": \"Days Name\",\n    \"description\": \"Convert hours to days with customizable options.\",\n    \"shortDescription\": \"Convert hours to days\",\n    \"title\": \"Convert Hours to Days\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to convert hours to days. You can input hours as numbers or with units, and the tool will convert them to days. You can also choose to append the 'days' suffix to the output values.\",\n      \"title\": \"Convert Hours to Days\"\n    }\n  },\n  \"convertSecondsToTime\": {\n    \"addPadding\": \"Add Padding\",\n    \"addPaddingDescription\": \"Add zero padding to hours, minutes, and seconds.\",\n    \"description\": \"Convert seconds to a readable time format (hours:minutes:seconds). Enter the number of seconds to get the formatted time.\",\n    \"shortDescription\": \"Convert seconds to time format\",\n    \"timePadding\": \"Time Padding\",\n    \"title\": \"Convert Seconds to Time\",\n    \"toolInfo\": {\n      \"title\": \"What is a {{title}}?\"\n    }\n  },\n  \"convertTimeToSeconds\": {\n    \"description\": \"Convert formatted time (HH:MM:SS) to seconds.\",\n    \"inputTitle\": \"Input time\",\n    \"resultTitle\": \"Seconds\",\n    \"shortDescription\": \"Convert time format to seconds\",\n    \"title\": \"Convert Time to Seconds\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to convert formatted time strings (HH:MM:SS) to seconds. It's useful for calculating durations and time intervals.\",\n      \"title\": \"Convert Time to Seconds\"\n    }\n  },\n  \"convertUnixToDate\": {\n    \"addUtcLabel\": \"Add 'UTC' suffix\",\n    \"addUtcLabelDescription\": \"Display 'UTC' after the converted date (only for UTC mode)\",\n    \"description\": \"Convert a Unix timestamp to a human-readable date.\",\n    \"outputOptions\": \"Output Options\",\n    \"shortDescription\": \"Convert Unix timestamp to date\",\n    \"title\": \"Convert Unix to Date\",\n    \"toolInfo\": {\n      \"description\": \"This tool converts a Unix timestamp (in seconds) into a human-readable date format (e.g., YYYY-MM-DD HH:MM:SS). It supports both local and UTC output, making it useful for quickly interpreting timestamps from logs, APIs, or systems that use Unix time.\",\n      \"title\": \"Convert Unix to Date\"\n    },\n    \"useLocalTime\": \"Use Local Time\",\n    \"useLocalTimeDescription\": \"Show converted date in your local timezone instead of UTC\",\n    \"withLabel\": \"Options\"\n  },\n  \"crontabGuru\": {\n    \"description\": \"Generate and understand cron expressions. Create cron schedules for automated tasks and system jobs.\",\n    \"shortDescription\": \"Generate and understand cron expressions\",\n    \"title\": \"Crontab Guru\"\n  },\n  \"timeBetweenDates\": {\n    \"description\": \"Calculate the time difference between two dates. Get the exact duration in days, hours, minutes, and seconds.\",\n    \"endDate\": \"End Date\",\n    \"endDateTime\": \"End Date & Time\",\n    \"endTime\": \"End Time\",\n    \"endTimezone\": \"End Timezone\",\n    \"shortDescription\": \"Calculate time between two dates\",\n    \"startDate\": \"Start Date\",\n    \"startDateTime\": \"Start Date & Time\",\n    \"startTime\": \"Start Time\",\n    \"startTimezone\": \"Start Timezone\",\n    \"title\": \"Time Between Dates\",\n    \"toolInfo\": {\n      \"description\": \"Calculate the exact time difference between two dates and times, with support for different timezones. This tool provides a detailed breakdown of the time difference in various units (years, months, days, hours, minutes, and seconds).\",\n      \"title\": \"Time Between Dates Calculator\"\n    }\n  },\n  \"truncateClockTime\": {\n    \"description\": \"Truncate clock time to remove seconds or minutes. Round time to the nearest hour, minute, or custom interval.\",\n    \"printDroppedComponents\": \"Print Dropped Components\",\n    \"shortDescription\": \"Truncate clock time to specified precision\",\n    \"timePadding\": \"Time Padding\",\n    \"title\": \"Truncate Clock Time\",\n    \"toolInfo\": {\n      \"title\": \"What is a {{title}}?\"\n    },\n    \"truncateMinutesAndSeconds\": \"Truncate Minutes and Seconds\",\n    \"truncateMinutesAndSecondsDescription\": \"Drop both – the minutes and seconds components from each clock time.\",\n    \"truncateOnlySeconds\": \"Truncate Only Seconds\",\n    \"truncateOnlySecondsDescription\": \"Drop the seconds component from each clock time.\",\n    \"truncationSide\": \"Truncation Side\",\n    \"useZeroPadding\": \"Use Zero Padding\",\n    \"zeroPaddingDescription\": \"Make all time components always be two digits wide.\",\n    \"zeroPrintDescription\": \"Display the dropped parts as zero values \\\"00\\\".\",\n    \"zeroPrintTruncatedParts\": \"Zero-print Truncated Parts\"\n  },\n  \"convertTimeToDecimal\": {\n    \"title\": \"Convert time to decimal\",\n    \"description\": \"Convert a formatted time duration (HH:MM:SS) into a decimal hour value.\",\n    \"shortDescription\": \"Convert human time to decimal time\",\n    \"longDescription\": \"Convert a formatted time string (HH:MM:SS or HH:MM) into its decimal-hour equivalent. Hours can be any positive number, while minutes and seconds accept values from 0-59 and can be single or double digits (e.g., '12:5' or '12:05'). This function interprets hours, minutes, and seconds, then calculates the total duration as a single decimal value. It is useful for productivity tracking, payroll calculations, time-based billing, data analysis, or any workflow that requires converting human-readable time into a numerical format that can be easily summed, compared, or processed. For example, '03:26:00' becomes 3.43 hours, and '12:5' becomes 12.08 hours.\"\n  },\n  \"discordTimestamp\": {\n    \"name\": \"Discord Timestamp Generator\",\n    \"description\": \"Convert dates and times into Discord's special timestamp format that automatically adapts to each user's local timezone and language\",\n    \"shortDescription\": \"Generate Discord timestamps from dates\",\n    \"inputTitle\": \"Input Datetimes\",\n    \"outputTitle\": \"Discord Timestamps\",\n    \"formats\": {\n      \"title\": \"Timestamp Format\",\n      \"description\": \"Choose how the timestamp will appear in Discord — hover over each option to see an example\",\n      \"short_time\": \"Short Time (ex: 14:30)\",\n      \"long_time\": \"Long Time (ex: 14:30:00)\",\n      \"short_date\": \"Short Date (ex: 01/01/2025)\",\n      \"long_date\": \"Long Date (ex: 1 January 2025)\",\n      \"short_datetime\": \"Short Date & Time (ex: 1 January 2025 14:30)\",\n      \"long_datetime\": \"Long Date & Time (ex: Monday, 1 January 2025 14:30)\",\n      \"relative\": \"Relative (ex: 2 hours ago)\"\n    },\n    \"utc\": {\n      \"title\": \"Input Options\",\n      \"label\": \"Enforce UTC\",\n      \"description\": \"Treat input datetimes as UTC. Disable to use your local timezone instead\"\n    },\n    \"toolInfo\": {\n      \"title\": \"What is a Discord Timestamp?\",\n      \"description\": \"Discord timestamps are a special syntax that displays dates and times dynamically. Instead of showing a fixed time, they automatically convert to each viewer's local timezone and language — so everyone sees the correct time for their location. Simply enter one datetime per line, choose a format, and paste the results directly into Discord. The tool processes each line independently — blank lines are preserved in the output, invalid lines are flagged with ❌, and valid datetimes are converted instantly. Enable UTC mode to ensure consistent results regardless of your local timezone.\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/en/translation.json",
    "content": "{\n  \"audio\": {\n    \"changeSpeed\": {\n      \"description\": \"Change the playback speed of audio files. Speed up or slow down audio while maintaining pitch.\",\n      \"name\": \"Change audio speed\",\n      \"shortDescription\": \"Change the speed of audio files\"\n    },\n    \"extractAudio\": {\n      \"description\": \"Extract the audio track from a video file and save it as a separate audio file in your chosen format (AAC, MP3, WAV).\",\n      \"name\": \"Extract audio\",\n      \"shortDescription\": \"Extract audio from video files (MP4, MOV, etc.) to AAC, MP3, or WAV.\"\n    }\n  },\n  \"baseFileInput\": {\n    \"copyFailed\": \"Failed to copy: {{error}}\",\n    \"dropFileHere\": \"Drop your {{type}} here\",\n    \"fileCopied\": \"File copied\",\n    \"selectFileDescription\": \"Click here to select a {{type}} from your device, press Ctrl+V to use a {{type}} from your clipboard, or drag and drop a file from desktop\"\n  },\n  \"converters\": {\n    \"audioconverter\": {\n      \"title\": \"Audio Converter\",\n      \"description\": \"Convert audio files between different formats.\",\n      \"shortDescription\": \"Convert audio files to various formats.\",\n      \"longDescription\": \"This tool allows you to convert audio files from one format to another, supporting a wide range of audio formats for seamless conversion.\"\n    }\n  },\n  \"categories\": {\n    \"audio\": {\n      \"description\": \"Tools for working with audio – extract audio from video, adjusting audio speed, merging multiple audio files and much more.\",\n      \"title\": \"Audio Tools\"\n    },\n    \"converters\": {\n      \"description\": \"Tools for converting data between different formats – convert images, audio, video, text, and more.\",\n      \"title\": \"Converter Tools\"\n    },\n    \"csv\": {\n      \"description\": \"Tools for working with CSV files - convert CSV to different formats, manipulate CSV data, validate CSV structure, and process CSV files efficiently.\",\n      \"title\": \"CSV Tools\"\n    },\n    \"gif\": {\n      \"description\": \"Tools for working with GIF animations – create transparent GIFs, extract GIF frames, add text to GIF, crop, rotate, reverse GIFs, and much more.\",\n      \"title\": \"GIF Tools\"\n    },\n    \"image-generic\": {\n      \"description\": \"Tools for working with pictures – compress, resize, crop, convert to JPG, rotate, remove background and much more.\",\n      \"title\": \"Image Tools\"\n    },\n    \"json\": {\n      \"description\": \"Tools for working with JSON data structures – prettify and minify JSON objects, flatten JSON arrays, stringify JSON values, analyze data, and much more\",\n      \"title\": \"JSON Tools\"\n    },\n    \"list\": {\n      \"description\": \"Tools for working with lists – sort, reverse, randomize lists, find unique and duplicate list items, change list item separators, and much more.\",\n      \"title\": \"List Tools\"\n    },\n    \"number\": {\n      \"description\": \"Tools for working with numbers – generate number sequences, convert numbers to words and words to numbers, sort, round, factor numbers, and much more.\",\n      \"title\": \"Number Tools\"\n    },\n    \"pdf\": {\n      \"description\": \"Tools for working with PDF files - extract text from PDFs, convert PDFs to other formats, manipulate PDFs, and much more.\",\n      \"title\": \"PDF Tools\"\n    },\n    \"png\": {\n      \"description\": \"Tools for working with PNG images – convert PNGs to JPGs, create transparent PNGs, change PNG colors, crop, rotate, resize PNGs, and much more.\",\n      \"title\": \"PNG Tools\"\n    },\n    \"seeAll\": \"See all {{title}}\",\n    \"string\": {\n      \"description\": \"Tools for working with text – convert text to images, find and replace text, split text into fragments, join text lines, repeat text, and much more.\",\n      \"title\": \"Text Tools\"\n    },\n    \"time\": {\n      \"description\": \"Tools for working with time and date – calculate time differences, convert between time zones, format dates, generate date sequences, and much more.\",\n      \"title\": \"Time Tools\"\n    },\n    \"try\": \"Try {{title}}\",\n    \"video\": {\n      \"description\": \"Tools for working with videos – extract frames from videos, create GIFs from videos, convert videos to different formats, and much more.\",\n      \"title\": \"Video Tools\"\n    },\n    \"xml\": {\n      \"description\": \"Tools for working with XML data structures - viewer, beautifier, validator and much more\",\n      \"title\": \"XML Tools\"\n    }\n  },\n  \"csv\": {\n    \"findIncompleteCsvRecords\": {\n      \"description\": \"Just upload your CSV file in the form below and this tool will automatically check if none of the rows or columns are missing values. In the tool options, you can adjust the input file format (specify the delimiter, quote character, and comment character). Additionally, you can enable checking for empty values, skip empty lines, and set a limit on the number of error messages in the output.\",\n      \"name\": \"Find incomplete CSV records\",\n      \"shortDescription\": \"Quickly find rows and columns in CSV that are missing values.\"\n    }\n  },\n  \"hero\": {\n    \"brand\": \"OmniTools\",\n    \"description\": \"Boost your productivity with OmniTools, the ultimate toolkit for getting things done quickly! Access thousands of user-friendly utilities for editing images, text, lists, and data, all directly from your browser.\",\n    \"examples\": {\n      \"calculateNumberSum\": \"Calculate number sum\",\n      \"changeGifSpeed\": \"Change GIF speed\",\n      \"compressPng\": \"Compress PNG\",\n      \"createTransparentImage\": \"Create a transparent image\",\n      \"prettifyJson\": \"Prettify JSON\",\n      \"sortList\": \"Sort a list\",\n      \"splitPdf\": \"Split PDF\",\n      \"splitText\": \"Split a text\",\n      \"trimVideo\": \"Trim video\"\n    },\n    \"searchPlaceholder\": \"Search all tools\",\n    \"title\": \"Get Things Done Quickly with\"\n  },\n  \"inputFooter\": {\n    \"clear\": \"Clear\",\n    \"copyToClipboard\": \"Copy to clipboard\",\n    \"importFromFile\": \"Import from file\"\n  },\n  \"list\": {\n    \"group\": {\n      \"description\": \"World's simplest browser-based utility for grouping list items. Input your list and specify grouping criteria to organize items into logical groups. Perfect for categorizing data, organizing information, or creating structured lists. Supports custom separators and various grouping options.\",\n      \"name\": \"Group\",\n      \"shortDescription\": \"Group list items by common properties\"\n    },\n    \"reverse\": {\n      \"description\": \"This is a super simple browser-based application prints all list items in reverse. The input items can be separated by any symbol and you can also change the separator of the reversed list items.\",\n      \"name\": \"Reverse\",\n      \"shortDescription\": \"Quickly reverse a list\"\n    },\n    \"sort\": {\n      \"description\": \"This is a super simple browser-based application that sorts items in a list and arranges them in increasing or decreasing order. You can sort the items alphabetically, numerically, or by their length. You can also remove duplicate and empty items, as well as trim individual items that have whitespace around them. You can use any separator character to separate the input list items or alternatively use a regular expression to separate them. Additionally, you can create a new delimiter for the sorted output list.\",\n      \"name\": \"Sort\",\n      \"shortDescription\": \"Quickly sort a list\"\n    }\n  },\n  \"navbar\": {\n    \"buyMeACoffee\": \"Buy me a coffee\",\n    \"hireMe\": \"Hire me\",\n    \"home\": \"Home\",\n    \"tools\": \"Tools\"\n  },\n  \"number\": {\n    \"generate\": {\n      \"description\": \"Quickly calculate a list of integers in your browser. To get your list, just specify the first integer, change value and total count in the options below, and this utility will generate that many integers\",\n      \"name\": \"Generate numbers\",\n      \"shortDescription\": \"Quickly calculate a list of integers in your browser\"\n    },\n    \"sum\": {\n      \"description\": \"This is a super simple browser-based application that sums numbers. The input numbers can be separated by any symbol and you can also change the separator of the summed numbers.\",\n      \"name\": \"Sum numbers\",\n      \"shortDescription\": \"Quickly sum a list of numbers\"\n    }\n  },\n  \"numericInputWithUnit\": {\n    \"unit\": \"Unit\"\n  },\n  \"pdf\": {\n    \"compressPdf\": {\n      \"description\": \"Reduce PDF file size while maintaining quality using Ghostscript\",\n      \"name\": \"Compress PDF\",\n      \"shortDescription\": \"Compress PDF files securely in your browser\"\n    },\n    \"mergePdf\": {\n      \"description\": \"Combine multiple PDF files into a single document.\",\n      \"name\": \"Merge PDF\",\n      \"shortDescription\": \"Merge multiple PDF files into a single document\"\n    },\n    \"pdfToEpub\": {\n      \"description\": \"Transform PDF documents into EPUB files for better e-reader compatibility.\",\n      \"name\": \"PDF to EPUB\",\n      \"shortDescription\": \"Convert PDF files to EPUB format\"\n    },\n    \"protectPdf\": {\n      \"description\": \"Add password protection to your PDF files securely in your browser\",\n      \"name\": \"Protect PDF\",\n      \"shortDescription\": \"Password protect PDF files securely\"\n    },\n    \"splitPdf\": {\n      \"description\": \"Extract specific pages from a PDF file using page numbers or ranges (e.g., 1,5-8)\",\n      \"name\": \"Split PDF\",\n      \"shortDescription\": \"Extract specific pages from a PDF file\"\n    }\n  },\n  \"resultFooter\": {\n    \"copy\": \"Copy to clipboard\",\n    \"download\": \"Download\"\n  },\n  \"string\": {\n    \"createPalindrome\": {\n      \"description\": \"World's simplest browser-based utility for creating palindromes from any text. Input text and instantly transform it into a palindrome that reads the same forward and backward. Perfect for word games, creating symmetrical text patterns, or exploring linguistic curiosities.\",\n      \"name\": \"Create palindrome\",\n      \"shortDescription\": \"Create text that reads the same forward and backward\"\n    },\n    \"palindrome\": {\n      \"description\": \"World's simplest browser-based utility for checking if text is a palindrome. Instantly verify if your text reads the same forward and backward. Perfect for word puzzles, linguistic analysis, or validating symmetrical text patterns. Supports various delimiters and multi-word palindrome detection.\",\n      \"name\": \"Palindrome\",\n      \"shortDescription\": \"Check if text reads the same forward and backward\"\n    },\n    \"repeat\": {\n      \"description\": \"This tool allows you to repeat a given text multiple times with an optional separator.\",\n      \"name\": \"Repeat text\",\n      \"shortDescription\": \"Repeat text multiple times\"\n    },\n    \"reverse\": {\n      \"description\": \"World's simplest browser-based utility for reversing text. Input any text and get it instantly reversed, character by character. Perfect for creating mirror text, analyzing palindromes, or playing with text patterns. Preserves spaces and special characters while reversing.\",\n      \"name\": \"Reverse\",\n      \"shortDescription\": \"Reverse any text character by character\"\n    },\n    \"toMorse\": {\n      \"description\": \"World's simplest browser-based utility for converting text to Morse code. Load your text in the input form on the left and you'll instantly get Morse code in the output area. Powerful, free, and fast. Load text – get Morse code.\",\n      \"name\": \"String To morse\",\n      \"shortDescription\": \"Quickly encode text to morse\"\n    },\n    \"uppercase\": {\n      \"description\": \"World's simplest browser-based utility for converting text to uppercase. Just input your text and it will be automatically converted to all capital letters. Perfect for creating headlines, emphasizing text, or standardizing text format. Supports various text formats and preserves special characters.\",\n      \"name\": \"Uppercase\",\n      \"shortDescription\": \"Convert text to uppercase letters\"\n    }\n  },\n  \"toolExamples\": {\n    \"subtitle\": \"Click to try!\",\n    \"title\": \"{{title}} Examples\"\n  },\n  \"toolFileResult\": {\n    \"copied\": \"File copied\",\n    \"copyFailed\": \"Failed to copy: {{error}}\",\n    \"loading\": \"Loading... This may take a moment.\",\n    \"result\": \"Result\"\n  },\n  \"toolHeader\": {\n    \"seeExamples\": \"See Examples\"\n  },\n  \"toolLayout\": {\n    \"allToolsTitle\": \"All {{type}}\"\n  },\n  \"toolMultiFileResult\": {\n    \"copied\": \"File copied\",\n    \"copyFailed\": \"Failed to copy: {{error}}\",\n    \"loading\": \"Loading... This may take a moment.\",\n    \"result\": \"Result\"\n  },\n  \"toolMultipleAudioInput\": {\n    \"inputTitle\": \"Input {{type}}\",\n    \"noFilesSelected\": \"No files selected\"\n  },\n  \"toolMultiplePdfInput\": {\n    \"inputTitle\": \"Input {{type}}\",\n    \"noFilesSelected\": \"No files selected\"\n  },\n  \"toolOptions\": {\n    \"title\": \"Tool options\"\n  },\n  \"toolTextInput\": {\n    \"copied\": \"Text copied\",\n    \"copyFailed\": \"Failed to copy: {{error}}\",\n    \"input\": \"Input text\",\n    \"placeholder\": \"Enter your text here...\"\n  },\n  \"toolTextResult\": {\n    \"copied\": \"Text copied\",\n    \"copyFailed\": \"Failed to copy: {{error}}\",\n    \"loading\": \"Loading... This may take a moment.\",\n    \"result\": \"Result\"\n  },\n  \"toolMultipleImageInput\": {\n    \"inputTitle\": \"Images Input\",\n    \"noFilesSelected\": \"No files selected\"\n  },\n  \"userTypes\": {\n    \"developers\": \"Developers\",\n    \"generalUsers\": \"General users\"\n  }\n}\n"
  },
  {
    "path": "public/locales/en/video.json",
    "content": "{\n  \"changeSpeed\": {\n    \"defaultMultiplier\": \"Default multiplier: 2 means 2x faster\",\n    \"description\": \"Change the playback speed of video files. Speed up or slow down videos while maintaining audio synchronization. Supports various speed multipliers and common video formats.\",\n    \"inputTitle\": \"Input Video\",\n    \"newVideoSpeed\": \"New Video Speed\",\n    \"resultTitle\": \"Edited Video\",\n    \"settingSpeed\": \"Setting Speed\",\n    \"shortDescription\": \"Change video playback speed\",\n    \"title\": \"Change Video Speed\",\n    \"toolInfo\": {\n      \"title\": \"What is a {{title}}?\"\n    }\n  },\n  \"compress\": {\n    \"default\": \"Default\",\n    \"description\": \"Compress videos by scaling them to different resolutions like 240p, 480p, 720p, etc. This tool helps reduce file size while maintaining acceptable quality. Supports common video formats like MP4, WebM, and OGG.\",\n    \"inputTitle\": \"Input Video\",\n    \"loadingText\": \"Compressing video...\",\n    \"lossless\": \"Lossless\",\n    \"quality\": \"Quality (CRF)\",\n    \"resolution\": \"Resolution\",\n    \"resultTitle\": \"Compressed Video\",\n    \"shortDescription\": \"Compress videos by scaling to different resolutions\",\n    \"title\": \"Compress Video\",\n    \"worst\": \"Worst\"\n  },\n  \"cropVideo\": {\n    \"cropCoordinates\": \"Crop Coordinates\",\n    \"croppingVideo\": \"Cropping Video\",\n    \"description\": \"Crop video to remove unwanted areas.\",\n    \"errorBeyondHeight\": \"Crop area extends beyond video height ({{height}}px)\",\n    \"errorBeyondWidth\": \"Crop area extends beyond video width ({{width}}px)\",\n    \"errorCroppingVideo\": \"Error cropping video. Please check parameters and video file.\",\n    \"errorLoadingDimensions\": \"Failed to load video dimensions\",\n    \"errorNonNegativeCoordinates\": \"X and Y coordinates must be non-negative\",\n    \"errorPositiveDimensions\": \"Width and height must be positive\",\n    \"height\": \"Height\",\n    \"inputTitle\": \"Input Video\",\n    \"loadVideoForDimensions\": \"Load a video to see dimensions\",\n    \"longDescription\": \"This tool allows you to crop video files to remove unwanted areas or focus on specific parts of the video. Useful for removing black bars, adjusting aspect ratios, or focusing on important content. Supports various video formats including MP4, MOV, and AVI.\",\n    \"resultTitle\": \"Cropped Video\",\n    \"shortDescription\": \"Crop video to remove unwanted areas\",\n    \"title\": \"Crop Video\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to crop video files to remove unwanted areas. You can specify the crop area by setting the X, Y coordinates and width, height dimensions.\",\n      \"title\": \"Crop Video\"\n    },\n    \"videoDimensions\": \"Video dimensions: {{width}} × {{height}} pixels\",\n    \"videoInformation\": \"Video Information\",\n    \"width\": \"Width\",\n    \"xCoordinate\": \"X (left)\",\n    \"yCoordinate\": \"Y (top)\"\n  },\n  \"flip\": {\n    \"description\": \"Flip video files horizontally or vertically. Mirror videos for special effects or correct orientation issues.\",\n    \"flippingVideo\": \"Flipping Video\",\n    \"horizontalLabel\": \"Horizontal (Mirror)\",\n    \"inputTitle\": \"Input Video\",\n    \"orientation\": \"Orientation\",\n    \"resultTitle\": \"Flipped Video\",\n    \"shortDescription\": \"Flip video horizontally or vertically\",\n    \"title\": \"Flip Video\",\n    \"verticalLabel\": \"Vertical (Upside Down)\"\n  },\n  \"gif\": {\n    \"changeSpeed\": {\n      \"description\": \"Change the playback speed of GIF animations. Speed up or slow down GIFs while maintaining smooth animation.\",\n      \"shortDescription\": \"Change GIF animation speed\",\n      \"title\": \"Change GIF Speed\"\n    }\n  },\n  \"loop\": {\n    \"description\": \"Create a looping video by repeating the original video multiple times.\",\n    \"inputTitle\": \"Input Video\",\n    \"loopingVideo\": \"Looping Video\",\n    \"loops\": \"Loops\",\n    \"numberOfLoops\": \"Number of Loops\",\n    \"resultTitle\": \"Looped Video\",\n    \"shortDescription\": \"Create looping video files\",\n    \"title\": \"Loop Video\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to create a looping video by repeating the original video multiple times. You can specify how many times the video should loop.\",\n      \"title\": \"What is a {{title}}?\"\n    }\n  },\n  \"mergeVideo\": {\n    \"description\": \"Combine multiple video files into one continuous video.\",\n    \"longDescription\": \"This tool allows you to merge or append multiple video files into a single continuous video. Simply upload your video files, arrange them in the desired order, and merge them into one file for easy sharing or editing.\",\n    \"shortDescription\": \"Append and merge videos easily.\",\n    \"title\": \"Merge videos\"\n  },\n  \"rotate\": {\n    \"180Degrees\": \"180° (Upside down)\",\n    \"270Degrees\": \"270° (90° Counter-clockwise)\",\n    \"90Degrees\": \"90° Clockwise\",\n    \"description\": \"Rotate video files by 90, 180, or 270 degrees. Correct video orientation or create special effects with precise rotation control.\",\n    \"inputTitle\": \"Input Video\",\n    \"resultTitle\": \"Rotated Video\",\n    \"rotatingVideo\": \"Rotating Video\",\n    \"rotation\": \"Rotation\",\n    \"shortDescription\": \"Rotate video by specified degrees\",\n    \"title\": \"Rotate Video\"\n  },\n  \"trim\": {\n    \"description\": \"Trim video files by specifying start and end times. Remove unwanted sections from the beginning or end of videos.\",\n    \"endTime\": \"End Time\",\n    \"inputTitle\": \"Input Video\",\n    \"resultTitle\": \"Trimmed Video\",\n    \"shortDescription\": \"Trim video by removing unwanted sections\",\n    \"startTime\": \"Start Time\",\n    \"timestamps\": \"Timestamps\",\n    \"title\": \"Trim Video\"\n  },\n  \"videoToGif\": {\n    \"description\": \"Convert video files to animated GIF format. Extract specific time ranges and create shareable animated images.\",\n    \"shortDescription\": \"Convert video to animated GIF\",\n    \"title\": \"Video to GIF\"\n  }\n}\n"
  },
  {
    "path": "public/locales/en/xml.json",
    "content": "{\n  \"xmlBeautifier\": {\n    \"description\": \"Format XML with proper indentation and spacing.\",\n    \"indentation\": \"Indentation\",\n    \"inputTitle\": \"Input XML\",\n    \"resultTitle\": \"Beautified XML\",\n    \"shortDescription\": \"Format and beautify XML code\",\n    \"title\": \"XML Beautifier\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to format XML data with proper indentation and spacing, making it more readable and easier to work with.\",\n      \"title\": \"XML Beautifier\"\n    },\n    \"useSpaces\": \"Use Spaces\",\n    \"useSpacesDescription\": \"Indent output with spaces\",\n    \"useTabs\": \"Use Tabs\",\n    \"useTabsDescription\": \"Indent output with tabs.\"\n  },\n  \"xmlValidator\": {\n    \"description\": \"Validate XML syntax and structure.\",\n    \"placeholder\": \"Paste or import XML here...\",\n    \"shortDescription\": \"Validate XML code for errors\",\n    \"title\": \"XML Validator\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to validate XML syntax and structure. It checks if the XML is well-formed and provides detailed error messages for any issues found.\",\n      \"title\": \"XML Validator\"\n    }\n  },\n  \"xmlViewer\": {\n    \"description\": \"View and explore XML structure in a tree format.\",\n    \"inputTitle\": \"Input XML\",\n    \"resultTitle\": \"XML Tree View\",\n    \"title\": \"XML Viewer\",\n    \"toolInfo\": {\n      \"description\": \"This tool allows you to view XML data in a hierarchical tree format, making it easier to explore and understand the structure of XML documents.\",\n      \"title\": \"XML Viewer\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/es/audio.json",
    "content": "{\n  \"changeSpeed\": {\n    \"description\": \"Cambia la velocidad de reproducción de los archivos de audio. Acelera o ralentiza el audio manteniendo el tono.\",\n    \"inputTitle\": \"Entrada de audio\",\n    \"newAudioSpeed\": \"Nueva velocidad de audio\",\n    \"outputFormat\": \"Formato de salida\",\n    \"resultTitle\": \"Audio editado\",\n    \"settingSpeed\": \"Ajuste de velocidad\",\n    \"shortDescription\": \"Cambiar la velocidad de los archivos de audio\",\n    \"speedDescription\": \"Multiplicador predeterminado: 2 significa 2 veces más rápido\",\n    \"title\": \"Cambiar la velocidad del audio\",\n    \"toolInfo\": {\n      \"title\": \"Qué es {{title}}?\"\n    }\n  },\n  \"extractAudio\": {\n    \"description\": \"Extraer pistas de audio de archivos de vídeo.\",\n    \"extractingAudio\": \"Extrayendo audio\",\n    \"inputTitle\": \"Vídeo de entrada\",\n    \"outputFormat\": \"Formato de salida\",\n    \"outputFormatDescription\": \"Seleccione el formato en el que se extraerá el audio.\",\n    \"resultTitle\": \"Audio extraído\",\n    \"shortDescription\": \"Extrae audio de archivos de vídeo (MP4, MOV, etc.) a AAC, MP3 o WAV.\",\n    \"title\": \"Extraer audio de un vídeo\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta te permite extraer la pista de audio de archivos de video. Puedes elegir entre diferentes formatos de audio, como AAC, MP3 y WAV.\",\n      \"title\": \"Qué es {{title}}?\"\n    }\n  },\n  \"mergeAudio\": {\n    \"description\": \"Combine varios archivos de audio en un solo archivo de audio concatenándolos en secuencia.\",\n    \"inputTitle\": \"Archivos de audio de entrada\",\n    \"longDescription\": \"Esta herramienta te permite fusionar varios archivos de audio en uno solo concatenándolos en el orden en que los subes. Es perfecta para combinar segmentos de podcast, pistas de música o cualquier archivo de audio que necesite unirse. Admite varios formatos de audio, como MP3, AAC y WAV.\",\n    \"mergingAudio\": \"Fusión de audio\",\n    \"outputFormat\": \"Formato de salida\",\n    \"resultTitle\": \"Audio fusionado\",\n    \"shortDescription\": \"Fusionar varios archivos de audio en uno (MP3, AAC, WAV).\",\n    \"title\": \"Fusionar audio\",\n    \"toolInfo\": {\n      \"title\": \"Qué es {{title}}?\"\n    }\n  },\n  \"trim\": {\n    \"description\": \"Corte y recorte archivos de audio para extraer segmentos específicos especificando las horas de inicio y finalización.\",\n    \"endTime\": \"Fin de los tiempos\",\n    \"endTimeDescription\": \"Hora de finalización en formato HH:MM:SS (por ejemplo, 00:01:30)\",\n    \"inputTitle\": \"Entrada de audio\",\n    \"longDescription\": \"Esta herramienta te permite recortar archivos de audio especificando la hora de inicio y la hora de fin. Puedes extraer segmentos específicos de archivos de audio más largos, eliminar partes no deseadas o crear clips más cortos. Compatible con varios formatos de audio, como MP3, AAC y WAV. Ideal para edición de podcasts, producción musical o cualquier otra necesidad de edición de audio.\",\n    \"outputFormat\": \"Formato de salida\",\n    \"resultTitle\": \"Audio recortado\",\n    \"shortDescription\": \"Recorta archivos de audio para extraer segmentos de tiempo específicos (MP3, AAC, WAV).\",\n    \"startTime\": \"Hora de inicio\",\n    \"startTimeDescription\": \"Hora de inicio en formato HH:MM:SS (por ejemplo, 00:00:30)\",\n    \"timeSettings\": \"Ajustes de hora\",\n    \"title\": \"Recortar audio\",\n    \"toolInfo\": {\n      \"title\": \"Qué es {{title}}?\"\n    },\n    \"trimmingAudio\": \"Recorte de audio\"\n  }\n}\n"
  },
  {
    "path": "public/locales/es/converters.json",
    "content": "{\n  \"audioConverter\": {\n    \"title\": \"Convertidor de Audio\",\n    \"description\": \"Convierte archivos de audio entre diferentes formatos.\",\n    \"shortDescription\": \"Convierte archivos de audio a varios formatos.\",\n    \"longDescription\": \"Esta herramienta le permite convertir archivos de audio de un formato a otro, con soporte para una amplia gama de formatos de audio para una conversión perfecta.\",\n    \"outputFormat\": \"Formato de Salida\",\n    \"outputFormatDescription\": \"Seleccione el formato de audio de salida deseado\",\n    \"inputTitle\": \"Entrada de Audio\",\n    \"outputTitle\": \"Audio Convertido\"\n  }\n}\n"
  },
  {
    "path": "public/locales/es/csv.json",
    "content": "{\n  \"changeCsvSeparator\": {\n    \"description\": \"Cambie el delimitador/separador en archivos CSV. Convierta entre diferentes formatos CSV, como coma, punto y coma, tabulación o separadores personalizados.\",\n    \"shortDescription\": \"Cambiar el delimitador del archivo CSV\",\n    \"title\": \"Cambiar el separador CSV\"\n  },\n  \"csvRowsToColumns\": {\n    \"description\": \"Esta herramienta convierte las filas de un archivo CSV (Valores Separados por Comas) en columnas. Extrae las líneas horizontales del CSV de entrada una por una, las gira 90 grados y las genera como columnas verticales, una tras otra, separadas por comas.', longDescription: 'Esta herramienta convierte las filas de un archivo CSV (Valores Separados por Comas) en columnas. Por ejemplo, si los datos CSV de entrada tienen 6 filas, la salida tendrá 6 columnas y los elementos de las filas se ordenarán de arriba a abajo. En un CSV bien formado, el número de valores en cada fila es el mismo. Sin embargo, si faltan campos en las filas, el programa puede corregirlos y usted puede elegir entre las opciones disponibles: rellenar los datos faltantes con elementos vacíos o reemplazarlos con elementos personalizados, como \\\"missing\\\", \\\"?\\\" o \\\"x\\\". Durante el proceso de conversión, la herramienta también limpia el archivo CSV de información innecesaria, como líneas vacías (líneas sin información visible) y comentarios. Para que la herramienta identifique correctamente los comentarios, en las opciones, puede especificar el símbolo al principio de la línea que inicia un comentario. Este símbolo suele ser una almohadilla \\\"#\\\" o una doble barra \\\"//\\\". ¡Csv-abuloso!\",\n    \"longDescription\": \"Esta herramienta convierte las filas de un archivo CSV (valores separados por comas) en columnas. Por ejemplo, si los datos CSV de entrada tienen 6 filas, la salida tendrá 6 columnas y los elementos de las filas se ordenarán de arriba a abajo. En un CSV bien formado, el número de valores en cada fila es el mismo. Sin embargo, si faltan campos en las filas, el programa puede corregirlos y usted puede elegir entre las opciones disponibles: rellenar los datos faltantes con elementos vacíos o reemplazarlos con elementos personalizados, como\",\n    \"shortDescription\": \"Convertir filas CSV en columnas.\",\n    \"title\": \"Convertir filas CSV en columnas\"\n  },\n  \"csvToJson\": {\n    \"columnSeparator\": \"Separador de columnas (p. ej., , ; \\\\t)\",\n    \"commentSymbol\": \"Símbolo de comentario (p. ej., #)\",\n    \"conversionOptions\": \"Opciones de conversión\",\n    \"description\": \"Convierte archivos CSV a formato JSON con opciones personalizables para delimitadores, comillas y formato de salida. Admite encabezados, comentarios y conversión dinámica de tipos.\",\n    \"dynamicTypes\": \"Tipos dinámicos\",\n    \"dynamicTypesDescription\": \"Convierte automáticamente números y valores booleanos\",\n    \"error\": \"Error\",\n    \"errorParsing\": \"Error al analizar CSV: {{error}}\",\n    \"fieldQuote\": \"Cita de campo (por ejemplo, \\\")\",\n    \"inputCsvFormat\": \"Formato CSV de entrada\",\n    \"inputTitle\": \"CSV de entrada\",\n    \"invalidCsvFormat\": \"Formato CSV no válido\",\n    \"resultTitle\": \"Salida JSON\",\n    \"shortDescription\": \"Convierte datos CSV al formato JSON.\",\n    \"skipEmptyLines\": \"Saltar líneas vacías\",\n    \"skipEmptyLinesDescription\": \"Ignorar líneas vacías en el CSV de entrada\",\n    \"title\": \"Convertir CSV a JSON\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta transforma archivos de valores separados por comas (CSV) en estructuras de datos de notación de objetos JavaScript (JSON). Admite varios formatos CSV con delimitadores personalizables, comillas y símbolos de comentario. El conversor puede tratar la primera fila como encabezado, omitir líneas vacías y detectar automáticamente tipos de datos como números y booleanos. El JSON resultante puede utilizarse para la migración de datos, la creación de copias de seguridad o como entrada para otras aplicaciones.\",\n      \"title\": \"¿Qué es un convertidor de CSV a JSON?\"\n    },\n    \"useHeaders\": \"Usar encabezados\",\n    \"useHeadersDescription\": \"Tratar la primera fila como encabezados de columna\"\n  },\n  \"csvToTsv\": {\n    \"description\": \"Sube tu archivo CSV en el formulario a continuación y se convertirá automáticamente a un archivo TSV. En las opciones de la herramienta, puedes personalizar el formato CSV de entrada: especifica el delimitador de campo, las comillas y el símbolo de comentario, omite las líneas vacías del CSV y elige si deseas conservar los encabezados de columna del CSV.\",\n    \"longDescription\": \"Esta herramienta transforma datos de valores separados por comas (CSV) en datos de valores separados por tabulaciones (TSV). Tanto CSV como TSV son formatos de archivo populares para almacenar datos tabulares, pero utilizan diferentes delimitadores para separar los valores: CSV usa comas (\",\n    \"shortDescription\": \"Convierte datos CSV al formato TSV.\",\n    \"title\": \"Convertir CSV a TSV\"\n  },\n  \"csvToXml\": {\n    \"description\": \"Convierte archivos CSV al formato XML con opciones personalizables.\",\n    \"shortDescription\": \"Convertir datos CSV al formato XML.\",\n    \"title\": \"Convertir CSV a XML\"\n  },\n  \"csvToYaml\": {\n    \"description\": \"Simplemente cargue su archivo CSV en el formulario a continuación y se convertirá automáticamente a un archivo YAML. En las opciones de la herramienta, puede especificar el carácter delimitador de campo, la comilla de campo y el carácter de comentario para adaptar la herramienta a formatos CSV personalizados. Además, puede seleccionar el formato YAML de salida: uno que conserve o excluya los encabezados CSV.\",\n    \"longDescription\": \"Esta herramienta transforma datos CSV (valores separados por comas) en datos YAML (un lenguaje de marcado más). CSV es un formato tabular simple que se utiliza para representar tipos de datos matriciales compuestos por filas y columnas. YAML, por otro lado, es un formato más avanzado (en realidad, un superconjunto de JSON), que crea datos más legibles para la serialización y admite listas, diccionarios y objetos anidados. Este programa admite varios formatos CSV de entrada: los datos de entrada pueden estar separados por comas (predeterminado), punto y coma, barras verticales o usar un delimitador completamente diferente. Puede especificar el delimitador exacto que usan sus datos en las opciones. De igual forma, en las opciones, puede especificar el carácter de comillas que se utiliza para encapsular los campos CSV (por defecto, un símbolo de comillas dobles). También puede omitir líneas que comiencen con comentarios especificando los símbolos de comentario en las opciones. Esto le permite mantener sus datos limpios al omitir líneas innecesarias. Hay dos maneras de convertir CSV a YAML. El primer método convierte cada fila del CSV en una lista YAML. El segundo método extrae los encabezados de la primera fila del CSV y crea objetos YAML con claves basadas en estos encabezados. También puedes personalizar el formato YAML de salida especificando el número de espacios para la sangría de las estructuras YAML. Si necesitas realizar la conversión inversa, es decir, transformar YAML a CSV, puedes usar nuestra herramienta Convertir YAML a CSV. ¡Csv-abroso!\",\n    \"shortDescription\": \"Convierte rápidamente un archivo CSV en un archivo YAML.\",\n    \"title\": \"Convertir CSV a YAML\"\n  },\n  \"findIncompleteCsvRecords\": {\n    \"checkingOptions\": \"Opciones de comprobación\",\n    \"commentCharacterDescription\": \"Introduzca el carácter que indica el inicio de una línea de comentario. Las líneas que comiencen con este símbolo se omitirán.\",\n    \"csvInputOptions\": \"Opciones de entrada CSV\",\n    \"csvSeparatorDescription\": \"Introduzca el carácter utilizado para delimitar columnas en el archivo de entrada CSV.\",\n    \"deleteLinesWithNoData\": \"Eliminar líneas sin datos\",\n    \"deleteLinesWithNoDataDescription\": \"Eliminar líneas vacías del archivo de entrada CSV.\",\n    \"description\": \"Simplemente cargue su archivo CSV en el formulario a continuación y esta herramienta comprobará automáticamente si ninguna fila o columna tiene valores faltantes. En las opciones de la herramienta, puede ajustar el formato del archivo de entrada (especifique el delimitador, las comillas y el carácter de comentario). Además, puede activar la comprobación de valores vacíos, omitir líneas vacías y limitar el número de mensajes de error en la salida.\",\n    \"findEmptyValues\": \"Encontrar valores vacíos\",\n    \"findEmptyValuesDescription\": \"Mostrar un mensaje sobre los campos CSV que están vacíos (no son campos faltantes, sino campos que no contienen nada).\",\n    \"inputTitle\": \"CSV de entrada\",\n    \"limitNumberOfMessages\": \"Limitar el número de mensajes\",\n    \"messageLimitDescription\": \"Establezca el límite del número de mensajes en la salida.\",\n    \"quoteCharacterDescription\": \"Introduzca el carácter de comillas utilizado para citar los campos de entrada CSV.\",\n    \"resultTitle\": \"Estado CSV\",\n    \"shortDescription\": \"Encuentre rápidamente filas y columnas en CSV que tengan valores faltantes.\",\n    \"title\": \"Encontrar registros CSV incompletos\",\n    \"toolInfo\": {\n      \"title\": \"¿Qué es un? {{title}}?\"\n    }\n  },\n  \"insertCsvColumns\": {\n    \"appendColumns\": \"Añadir columnas\",\n    \"commentCharacterDescription\": \"Introduzca el carácter que indica el inicio de una línea de comentario. Las líneas que comiencen con este símbolo se omitirán.\",\n    \"csvOptions\": \"Opciones CSV\",\n    \"csvSeparator\": \"Separador CSV\",\n    \"csvToInsert\": \"CSV para insertar\",\n    \"csvToInsertDescription\": \"Introduzca una o más columnas que desee insertar en el CSV. El carácter utilizado para delimitar las columnas debe ser el mismo que el del archivo CSV de entrada. Nota: Se ignorarán las líneas en blanco.\",\n    \"customFillDescription\": \"Si el archivo CSV de entrada está incompleto (valores faltantes), ¿cómo agregar campos vacíos o símbolos personalizados a los registros para crear un CSV bien formado?\",\n    \"customFillValueDescription\": \"Utilice este valor personalizado para completar los campos que faltan. (Solo funciona con el modo \\\"Valores personalizados\\\" mencionado anteriormente).\",\n    \"customPosition\": \"Posición personalizada\",\n    \"customPositionOptionsDescription\": \"Seleccione el método para insertar las columnas en el archivo CSV.\",\n    \"description\": \"Agregar nuevas columnas a los datos CSV en posiciones específicas.\",\n    \"fillWithCustomValues\": \"Rellenar con valores aduaneros\",\n    \"fillWithEmptyValues\": \"Rellenar con valores vacíos\",\n    \"headerName\": \"Nombre del encabezado\",\n    \"headerNameDescription\": \"Encabezado de la columna después de la cual desea insertar columnas.\",\n    \"inputTitle\": \"CSV de entrada\",\n    \"insertingPositionDescription\": \"Especifique dónde insertar las columnas en el archivo CSV.\",\n    \"position\": \"Posición\",\n    \"positionOptions\": \"Opciones de posición\",\n    \"prependColumns\": \"Anteponer columnas\",\n    \"quoteCharDescription\": \"Introduzca el carácter de comillas utilizado para citar los campos de entrada CSV.\",\n    \"resultTitle\": \"CSV de salida\",\n    \"rowNumberDescription\": \"Número de la columna después de la cual desea insertar columnas.\",\n    \"separatorDescription\": \"Introduzca el carácter utilizado para delimitar columnas en el archivo de entrada CSV.\",\n    \"shortDescription\": \"Inserte rápidamente una o más columnas nuevas en cualquier lugar de un archivo CSV.\",\n    \"title\": \"Insertar columnas CSV\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite insertar nuevas columnas en datos CSV en posiciones específicas. Puede anteponer, anexar o insertar columnas en posiciones personalizadas según los nombres de encabezado o los números de columna.\",\n      \"title\": \"Insertar columnas CSV\"\n    }\n  },\n  \"swapCsvColumns\": {\n    \"description\": \"Simplemente cargue su archivo CSV en el formulario a continuación, especifique las columnas que desea intercambiar y la herramienta cambiará automáticamente la posición de dichas columnas en el archivo de salida. En las opciones de la herramienta, puede especificar las posiciones o los nombres de las columnas que desea intercambiar, así como corregir datos incompletos y, opcionalmente, eliminar registros vacíos y comentados.\",\n    \"longDescription\": \"Esta herramienta reorganiza los datos CSV intercambiando la posición de sus columnas. Intercambiar columnas mejora la legibilidad de un archivo CSV al colocar los datos de uso frecuente juntos o al frente para facilitar la comparación y edición. Por ejemplo, puede intercambiar la primera columna con la última o la segunda con la tercera. Para intercambiar columnas según su posición, seleccione\",\n    \"shortDescription\": \"Reordenar columnas CSV.\",\n    \"title\": \"Intercambiar columnas CSV\"\n  },\n  \"transposeCsv\": {\n    \"description\": \"Simplemente cargue su archivo CSV en el formulario a continuación y esta herramienta lo transpondrá automáticamente. En las opciones de la herramienta, puede especificar el carácter que inicia las líneas de comentario en el CSV para eliminarlas. Además, si el CSV está incompleto (valores faltantes), puede reemplazarlos con el carácter vacío o un carácter personalizado.\",\n    \"longDescription\": \"Esta herramienta transpone valores separados por comas (CSV). Trata el CSV como una matriz de datos e invierte todos los elementos a lo largo de la diagonal principal. El resultado contiene los mismos datos CSV que la entrada, pero ahora todas las filas se han convertido en columnas y todas las columnas en filas. Tras la transposición, el archivo CSV tendrá dimensiones opuestas. Por ejemplo, si el archivo de entrada tiene 4 columnas y 3 filas, el archivo de salida tendrá 3 columnas y 4 filas. Durante la conversión, el programa también elimina las líneas innecesarias y corrige los datos incompletos. En concreto, la herramienta elimina automáticamente todos los registros vacíos y los comentarios que empiezan por un carácter específico, que se puede configurar en la opción. Además, si los datos CSV se corrompen o se pierden, la utilidad completa el archivo con campos vacíos o campos personalizados que se pueden especificar en las opciones. ¡Csv-abuloso!\",\n    \"shortDescription\": \"Transponer rápidamente un archivo CSV.\",\n    \"title\": \"Transponer CSV\"\n  },\n  \"tsvToJson\": {\n    \"description\": \"Convierte datos TSV (Valores Separados por Tabulaciones) a formato JSON. Transforma datos tabulares en objetos JSON estructurados.\",\n    \"shortDescription\": \"Convertir TSV a formato JSON\",\n    \"title\": \"TSV a JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/es/image.json",
    "content": "{\n  \"changeColors\": {\n    \"description\": \"Mundo\",\n    \"shortDescription\": \"Intercambia colores rápidamente en una imagen\",\n    \"title\": \"Cambiar colores en la imagen\"\n  },\n  \"changeOpacity\": {\n    \"description\": \"Ajusta fácilmente la transparencia de tus imágenes. Simplemente sube tu imagen, usa el control deslizante para establecer el nivel de opacidad deseado entre 0 (totalmente transparente) y 1 (totalmente opaco) y descarga la imagen modificada.\",\n    \"shortDescription\": \"Ajustar la transparencia de las imágenes\",\n    \"title\": \"Cambiar la opacidad de la imagen\"\n  },\n  \"compress\": {\n    \"compressedSize\": \"Tamaño comprimido\",\n    \"compressionOptions\": \"Opciones de compresión\",\n    \"description\": \"Reduce el tamaño del archivo de imagen manteniendo la calidad.\",\n    \"failedToCompress\": \"No se pudo comprimir la imagen. Inténtalo de nuevo.\",\n    \"fileSizes\": \"Tamaños de archivos\",\n    \"inputTitle\": \"Imagen de entrada\",\n    \"maxFileSizeDescription\": \"Tamaño máximo de archivo en megabytes\",\n    \"originalSize\": \"Tamaño original\",\n    \"qualityDescription\": \"Porcentaje de calidad de imagen (cuanto menor, menor es el tamaño del archivo)\",\n    \"resultTitle\": \"Imagen comprimida\",\n    \"shortDescription\": \"Comprima imágenes para reducir el tamaño del archivo manteniendo una calidad razonable.\",\n    \"title\": \"Comprimir imagen\"\n  },\n  \"compressPng\": {\n    \"description\": \"Este programa comprime imágenes PNG. Al pegar la imagen PNG en el área de entrada, el programa la comprimirá y mostrará el resultado en el área de salida. En las opciones, puede ajustar el nivel de compresión y consultar los tamaños de archivo de las imágenes antiguas y nuevas.\",\n    \"shortDescription\": \"Comprimir rápidamente un PNG\",\n    \"title\": \"Comprimir png\"\n  },\n  \"convertJgpToPng\": {\n    \"description\": \"Convierte rápidamente tus imágenes JPG a PNG. Simplemente importa tu imagen PNG en el editor de la izquierda.\",\n    \"shortDescription\": \"Convierte rápidamente tus imágenes JPG a PNG\",\n    \"title\": \"Convertir JPG a PNG\"\n  },\n  \"convertToJpg\": {\n    \"description\": \"Convierte varios formatos de imagen (PNG, GIF, TIF, PSD, SVG, WEBP, HEIC, RAW) a JPG con configuraciones de color de fondo y calidad personalizables.\",\n    \"shortDescription\": \"Convierte imágenes a JPG con control de calidad\",\n    \"title\": \"Convertir imágenes a JPG\"\n  },\n  \"createTransparent\": {\n    \"description\": \"Mundo\",\n    \"shortDescription\": \"Hacer que una imagen sea transparente rápidamente\",\n    \"title\": \"Crear PNG transparente\"\n  },\n  \"crop\": {\n    \"description\": \"Recortar imágenes para eliminar áreas no deseadas.\",\n    \"inputTitle\": \"Imagen de entrada\",\n    \"resultTitle\": \"Imagen recortada\",\n    \"shortDescription\": \"Recortar imágenes rápidamente.\",\n    \"title\": \"Recortar imagen\"\n  },\n  \"editor\": {\n    \"description\": \"Editor de imágenes avanzado con herramientas para recortar, rotar, anotar, ajustar colores y añadir marcas de agua. Edita tus imágenes con herramientas profesionales directamente en tu navegador.\",\n    \"shortDescription\": \"Edite imágenes con herramientas y funciones avanzadas\",\n    \"title\": \"Editor de imágenes\"\n  },\n  \"imageToText\": {\n    \"description\": \"Extraer texto de imágenes (JPG, PNG) mediante reconocimiento óptico de caracteres (OCR).\",\n    \"shortDescription\": \"Extraer texto de imágenes usando OCR.\",\n    \"title\": \"Imagen a texto (OCR)\"\n  },\n  \"qrCode\": {\n    \"description\": \"Genere códigos QR para diferentes tipos de datos: URL, texto, correo electrónico, teléfono, SMS, WiFi, vCard y más.\",\n    \"shortDescription\": \"Cree códigos QR personalizados para varios formatos de datos.\",\n    \"title\": \"Generador de códigos QR\"\n  },\n  \"removeBackground\": {\n    \"description\": \"Mundo\",\n    \"shortDescription\": \"Eliminar automáticamente los fondos de las imágenes\",\n    \"title\": \"Quitar el fondo de la imagen\"\n  },\n  \"resize\": {\n    \"description\": \"Cambiar el tamaño de las imágenes a diferentes dimensiones.\",\n    \"dimensionType\": \"Tipo de dimensión\",\n    \"heightDescription\": \"Altura (en píxeles)\",\n    \"inputTitle\": \"Imagen de entrada\",\n    \"maintainAspectRatio\": \"Mantener la relación de aspecto\",\n    \"maintainAspectRatioDescription\": \"Mantener la relación de aspecto original de la imagen.\",\n    \"percentage\": \"Porcentaje\",\n    \"percentageDescription\": \"Porcentaje del tamaño original (p. ej., 50 para la mitad del tamaño, 200 para el doble del tamaño)\",\n    \"resizeByPercentage\": \"Cambiar el tamaño por porcentaje\",\n    \"resizeByPercentageDescription\": \"Cambiar el tamaño especificando un porcentaje del tamaño original.\",\n    \"resizeByPixels\": \"Cambiar tamaño por píxeles\",\n    \"resizeByPixelsDescription\": \"Cambiar el tamaño especificando las dimensiones en píxeles.\",\n    \"resizeMethod\": \"Método de cambio de tamaño\",\n    \"resultTitle\": \"Imagen redimensionada\",\n    \"setHeight\": \"Establecer altura\",\n    \"setHeightDescription\": \"Especifique la altura en píxeles y calcule el ancho según la relación de aspecto.\",\n    \"setWidth\": \"Establecer ancho\",\n    \"setWidthDescription\": \"Especifique el ancho en píxeles y calcule la altura según la relación de aspecto.\",\n    \"shortDescription\": \"Cambie el tamaño de las imágenes fácilmente.\",\n    \"title\": \"Cambiar el tamaño de la imagen\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite redimensionar imágenes JPG, PNG, SVG o GIF. Puedes redimensionarlas especificando las dimensiones en píxeles o por porcentaje, con opciones para mantener la relación de aspecto original.\",\n      \"title\": \"Cambiar el tamaño de la imagen\"\n    },\n    \"widthDescription\": \"Ancho (en píxeles)\"\n  },\n  \"rotate\": {\n    \"description\": \"Girar una imagen en un ángulo específico.\",\n    \"shortDescription\": \"Girar una imagen fácilmente.\",\n    \"title\": \"Girar imagen\"\n  }\n}\n"
  },
  {
    "path": "public/locales/es/json.json",
    "content": "{\n  \"comparison\": {\n    \"description\": \"Compare dos objetos JSON para identificar diferencias en estructura y valores.\",\n    \"shortDescription\": \"Encuentra diferencias entre dos objetos JSON\",\n    \"title\": \"Comparar JSON\"\n  },\n  \"escapeJson\": {\n    \"description\": \"Escape de caracteres especiales en cadenas JSON. Convierta datos JSON a un formato de escape adecuado para una transmisión o almacenamiento seguros.\",\n    \"shortDescription\": \"Escapar caracteres especiales en JSON\",\n    \"title\": \"Escapar JSON\"\n  },\n  \"jsonToXml\": {\n    \"description\": \"Convierte datos JSON a formato XML. Transforma objetos JSON estructurados en documentos XML bien formados.\",\n    \"shortDescription\": \"Convertir JSON a formato XML\",\n    \"title\": \"JSON a XML\"\n  },\n  \"minify\": {\n    \"description\": \"Elimina todos los espacios en blanco innecesarios de JSON.\",\n    \"inputTitle\": \"Entrada JSON\",\n    \"resultTitle\": \"JSON minimizado\",\n    \"shortDescription\": \"Minificar JSON eliminando los espacios en blanco\",\n    \"title\": \"Minificar JSON\",\n    \"toolInfo\": {\n      \"description\": \"La minimización de JSON consiste en eliminar todos los espacios innecesarios de los datos JSON, manteniendo su validez. Esto incluye la eliminación de espacios, saltos de línea y sangrías innecesarios para el correcto análisis del JSON. La minimización reduce el tamaño de los datos JSON, lo que aumenta su eficiencia de almacenamiento y transmisión, manteniendo la misma estructura y valores de datos.\",\n      \"title\": \"¿Qué es la minificación de JSON?\"\n    }\n  },\n  \"prettify\": {\n    \"description\": \"Formatee JSON con sangría y espaciado adecuados.\",\n    \"indentation\": \"Sangría\",\n    \"inputTitle\": \"Entrada JSON\",\n    \"resultTitle\": \"JSON embellecido\",\n    \"shortDescription\": \"Formatear y embellecer el código JSON\",\n    \"title\": \"Embellecer JSON\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta le permite formatear datos JSON con sangría y espaciado adecuados, haciéndolos más legibles y fáciles de trabajar.\",\n      \"title\": \"Embellecer JSON\"\n    },\n    \"useSpaces\": \"Utilice espacios\",\n    \"useSpacesDescription\": \"Sangrar la salida con espacios\",\n    \"useTabs\": \"Usar pestañas\",\n    \"useTabsDescription\": \"Sangrar la salida con tabulaciones.\"\n  },\n  \"stringify\": {\n    \"description\": \"Convierte objetos JavaScript a formato de cadena JSON. Serializa estructuras de datos en cadenas JSON para su almacenamiento o transmisión.\",\n    \"shortDescription\": \"Convertir objetos en cadena JSON\",\n    \"title\": \"Convertir JSON en cadenas\"\n  },\n  \"validateJson\": {\n    \"description\": \"Comprueba si JSON es válido y está bien formado.\",\n    \"inputTitle\": \"Entrada JSON\",\n    \"invalidJson\": \"❌ {{error}}\",\n    \"resultTitle\": \"Resultado de la validación\",\n    \"shortDescription\": \"Validar el código JSON para detectar errores\",\n    \"title\": \"Validar JSON\",\n    \"toolInfo\": {\n      \"description\": \"JSON (Notación de Objetos JavaScript) es un formato ligero de intercambio de datos. La validación de JSON garantiza que la estructura de los datos cumpla con el estándar JSON. Un objeto JSON válido debe tener: - Nombres de propiedad entre comillas dobles. - Llaves {} correctamente equilibradas. - Sin comas finales después del último par clave-valor. - Anidamiento correcto de objetos y matrices. Esta herramienta revisa el JSON de entrada y proporciona información para ayudar a identificar y corregir errores comunes.\",\n      \"title\": \"¿Qué es la validación JSON?\"\n    },\n    \"validJson\": \"✅ JSON válido\"\n  }\n}\n"
  },
  {
    "path": "public/locales/es/list.json",
    "content": "{\n  \"duplicate\": {\n    \"concatenate\": \"Concatenar\",\n    \"concatenateDescription\": \"Concatenar copias (si no está marcada, los elementos se entrelazarán)\",\n    \"copyDescription\": \"Número de copias (puede ser fraccionario)\",\n    \"description\": \"La utilidad basada en navegador más sencilla del mundo para duplicar elementos de listas. Introduce tu lista y especifica los criterios de duplicación para crear copias de elementos. Perfecta para ampliar datos, realizar pruebas o crear patrones repetidos.\",\n    \"duplicationOptions\": \"Opciones de duplicación\",\n    \"error\": \"Error\",\n    \"example1Description\": \"Este ejemplo muestra cómo duplicar una lista de palabras.\",\n    \"example1Title\": \"Duplicación simple\",\n    \"example2Description\": \"Este ejemplo muestra cómo duplicar una lista en orden inverso.\",\n    \"example2Title\": \"Duplicación inversa\",\n    \"example3Description\": \"Este ejemplo muestra cómo entrelazar elementos en lugar de concatenarlos.\",\n    \"example3Title\": \"Elementos entretejidos\",\n    \"example4Description\": \"Este ejemplo muestra cómo duplicar una lista con un número fraccionario de copias.\",\n    \"example4Title\": \"Duplicación fraccionaria\",\n    \"examples\": {\n      \"fractional\": {\n        \"description\": \"Este ejemplo muestra cómo duplicar una lista con un número fraccionario de copias.\",\n        \"title\": \"Duplicación fraccionaria\"\n      },\n      \"interweave\": {\n        \"description\": \"Este ejemplo muestra cómo entrelazar elementos en lugar de concatenarlos.\",\n        \"title\": \"Elementos entretejidos\"\n      },\n      \"reverse\": {\n        \"description\": \"Este ejemplo muestra cómo duplicar una lista en orden inverso.\",\n        \"title\": \"Duplicación inversa\"\n      },\n      \"simple\": {\n        \"description\": \"Este ejemplo muestra cómo duplicar una lista de palabras.\",\n        \"title\": \"Duplicación simple\"\n      }\n    },\n    \"inputTitle\": \"Lista de entrada\",\n    \"joinSeparatorDescription\": \"Separador para unir la lista duplicada\",\n    \"resultTitle\": \"Lista duplicada\",\n    \"reverse\": \"Contrarrestar\",\n    \"reverseDescription\": \"Revertir los elementos duplicados\",\n    \"shortDescription\": \"Elementos de lista duplicados con criterios específicos\",\n    \"splitByRegex\": \"Dividir por expresión regular\",\n    \"splitBySymbol\": \"Dividir por símbolo\",\n    \"splitOptions\": \"Opciones divididas\",\n    \"splitSeparatorDescription\": \"Separador para dividir la lista\",\n    \"title\": \"Duplicado\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite duplicar elementos en una lista. Se puede especificar el número de copias (incluidos los valores fraccionarios), controlar si los elementos están concatenados o entrelazados, e incluso invertir la duplicación. Resulta útil para crear patrones repetidos, generar datos de prueba o ampliar listas con contenido predecible.\",\n      \"title\": \"Duplicación de listas\"\n    },\n    \"unknownError\": \"Se produjo un error desconocido\",\n    \"validation\": {\n      \"copyMustBeNumber\": \"El número de copias debe ser un número\",\n      \"copyMustBePositive\": \"El número de copias debe ser positivo\",\n      \"copyRequired\": \"Se requiere número de copias\",\n      \"joinSeparatorRequired\": \"Se requiere el separador de unión\",\n      \"separatorRequired\": \"Se requiere el separador\"\n    }\n  },\n  \"findMostPopular\": {\n    \"description\": \"La utilidad de navegador más sencilla del mundo para encontrar los elementos más populares en una lista. Introduce tu lista y obtén al instante los elementos que aparecen con más frecuencia. Perfecta para análisis de datos, identificación de tendencias o búsqueda de elementos comunes.\",\n    \"displayFormatDescription\": \"¿Cómo mostrar los elementos de la lista más populares?\",\n    \"displayOptions\": {\n      \"count\": \"Mostrar el número de artículos\",\n      \"percentage\": \"Mostrar porcentaje del artículo\",\n      \"total\": \"Mostrar el total del artículo\"\n    },\n    \"extractListItems\": \"¿Cómo extraer elementos de una lista?\",\n    \"ignoreItemCase\": \"Ignorar mayúsculas y minúsculas del artículo\",\n    \"ignoreItemCaseDescription\": \"Compara todos los elementos de la lista en minúsculas.\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"itemComparison\": \"Comparación de artículos\",\n    \"outputFormat\": \"Formato de salida del elemento superior\",\n    \"removeEmptyItems\": \"Eliminar elementos vacíos\",\n    \"removeEmptyItemsDescription\": \"Ignore los elementos vacíos de la comparación.\",\n    \"resultTitle\": \"Artículos más populares\",\n    \"shortDescription\": \"Encuentra los elementos que aparecen con más frecuencia\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Ordenar alfabéticamente\",\n      \"count\": \"Ordenar por conteo\"\n    },\n    \"sortingMethodDescription\": \"Seleccione un método de clasificación.\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Delimitar los elementos de la lista de entrada con una expresión regular.\",\n        \"title\": \"Utilice una expresión regular para dividir\"\n      },\n      \"symbol\": {\n        \"description\": \"Delimitar los elementos de la lista de entrada con un carácter.\",\n        \"title\": \"Utilice un símbolo para dividir\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Establezca un símbolo delimitador o una expresión regular.\",\n    \"title\": \"Encuentra los más populares\",\n    \"trimItems\": \"Recortar los elementos superiores de la lista\",\n    \"trimItemsDescription\": \"Elimine los espacios iniciales y finales antes de comparar elementos\"\n  },\n  \"findUnique\": {\n    \"caseSensitiveItems\": \"Elementos que distinguen entre mayúsculas y minúsculas\",\n    \"caseSensitiveItemsDescription\": \"Muestra los elementos con mayúsculas y minúsculas diferentes como elementos únicos en la lista.\",\n    \"delimiterDescription\": \"Establezca un símbolo delimitador o una expresión regular.\",\n    \"description\": \"La utilidad de navegador más sencilla del mundo para encontrar elementos únicos en una lista. Introduce tu lista y obtén al instante todos los valores únicos, eliminando los duplicados. Perfecta para la limpieza de datos, la deduplicación o la búsqueda de elementos distintos.\",\n    \"findAbsolutelyUniqueItems\": \"Encuentra artículos absolutamente únicos\",\n    \"findAbsolutelyUniqueItemsDescription\": \"Mostrar sólo aquellos elementos de la lista que existen en una sola copia.\",\n    \"inputListDelimiter\": \"Delimitador de lista de entrada\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"outputListDelimiter\": \"Delimitador de lista de salida\",\n    \"resultTitle\": \"Artículos únicos\",\n    \"shortDescription\": \"Encuentra artículos únicos en una lista\",\n    \"skipEmptyItems\": \"Omitir elementos vacíos\",\n    \"skipEmptyItemsDescription\": \"No incluya los elementos de lista vacíos en la salida.\",\n    \"title\": \"Encuentra algo único\",\n    \"trimItems\": \"Elementos de la lista de recortes\",\n    \"trimItemsDescription\": \"Elimine los espacios iniciales y finales antes de comparar elementos.\",\n    \"uniqueItemOptions\": \"Opciones de artículos únicos\"\n  },\n  \"group\": {\n    \"deleteEmptyItems\": \"Eliminar elementos vacíos\",\n    \"deleteEmptyItemsDescription\": \"Ignore los elementos vacíos y no los incluya en los grupos.\",\n    \"description\": \"La utilidad basada en navegador más sencilla del mundo para agrupar elementos de listas. Introduce tu lista y especifica los criterios de agrupación para organizar los elementos en grupos lógicos. Perfecta para categorizar datos, organizar información o crear listas estructuradas. Admite separadores personalizados y diversas opciones de agrupación.\",\n    \"emptyItemsAndPadding\": \"Elementos vacíos y relleno\",\n    \"groupNumberDescription\": \"Número de elementos en un grupo\",\n    \"groupSeparatorDescription\": \"Carácter separador de grupo\",\n    \"groupSizeAndSeparators\": \"Tamaño del grupo y separadores\",\n    \"inputItemSeparator\": \"Separador de elementos de entrada\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"itemSeparatorDescription\": \"Carácter separador de elementos\",\n    \"leftWrapDescription\": \"Símbolo de envoltura izquierda del grupo.\",\n    \"padNonFullGroups\": \"Grupos no llenos de Pad\",\n    \"padNonFullGroupsDescription\": \"Llene los grupos que no estén completos con un elemento personalizado (ingréselo a continuación).\",\n    \"paddingCharDescription\": \"Utilice este carácter o elemento para rellenar grupos que no estén completos.\",\n    \"resultTitle\": \"Elementos agrupados\",\n    \"rightWrapDescription\": \"Símbolo de envoltura derecha del grupo.\",\n    \"shortDescription\": \"Agrupar elementos de la lista por propiedades comunes\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Delimitar los elementos de la lista de entrada con una expresión regular.\",\n        \"title\": \"Utilice una expresión regular para dividir\"\n      },\n      \"symbol\": {\n        \"description\": \"Delimitar los elementos de la lista de entrada con un carácter.\",\n        \"title\": \"Utilice un símbolo para dividir\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Establezca un símbolo delimitador o una expresión regular.\",\n    \"title\": \"Grupo\"\n  },\n  \"reverse\": {\n    \"description\": \"Esta sencilla aplicación basada en navegador imprime todos los elementos de la lista en orden inverso. Los elementos de entrada se pueden separar con cualquier símbolo y también se puede cambiar el separador de los elementos de la lista invertidos.\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"itemSeparator\": \"Separador de artículos\",\n    \"itemSeparatorDescription\": \"Establezca un símbolo delimitador o una expresión regular.\",\n    \"outputListOptions\": \"Opciones de lista de salida\",\n    \"outputSeparatorDescription\": \"Separador de elementos de lista de salida.\",\n    \"resultTitle\": \"Lista invertida\",\n    \"shortDescription\": \"Invertir una lista rápidamente\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Delimitar los elementos de la lista de entrada con una expresión regular.\",\n        \"title\": \"Utilice una expresión regular para dividir\"\n      },\n      \"symbol\": {\n        \"description\": \"Delimitar los elementos de la lista de entrada con un carácter.\",\n        \"title\": \"Utilice un símbolo para dividir\"\n      }\n    },\n    \"splitterMode\": \"Modo divisor\",\n    \"title\": \"Contrarrestar\",\n    \"toolInfo\": {\n      \"description\": \"Con esta utilidad, puede invertir el orden de los elementos de una lista. La utilidad primero divide la lista de entrada en elementos individuales y luego los itera desde el último hasta el primero, imprimiendo cada elemento en la salida durante la iteración. La lista de entrada puede contener cualquier elemento que pueda representarse como datos textuales, incluyendo dígitos, números, cadenas, palabras, oraciones, etc. El separador de elementos de entrada también puede ser una expresión regular. Por ejemplo, la expresión regular /[;,]/ le permitirá usar elementos separados por coma o punto y coma. Los delimitadores de los elementos de las listas de entrada y salida se pueden personalizar en las opciones. Por defecto, tanto las listas de entrada como las de salida están separadas por comas. ¡Increíble!\",\n      \"title\": \"¿Qué es un inversor de listas?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"La utilidad basada en navegador más sencilla del mundo para rotar elementos de listas. Introduce tu lista y especifica el grado de rotación para desplazar los elementos un número determinado de posiciones. Perfecta para la manipulación de datos, desplazamientos circulares o reordenamiento de listas.\",\n    \"shortDescription\": \"Rotar elementos de la lista según posiciones específicas\",\n    \"title\": \"Girar\"\n  },\n  \"shuffle\": {\n    \"delimiterDescription\": \"Establezca un símbolo delimitador o una expresión regular.\",\n    \"description\": \"La utilidad de navegador más sencilla del mundo para ordenar aleatoriamente los elementos de una lista. Introduce tu lista y obtén al instante una versión aleatoria con elementos en orden aleatorio. Perfecta para crear variedad, comprobar la aleatoriedad o mezclar datos ordenados.\",\n    \"inputListSeparator\": \"Separador de lista de entrada\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"joinSeparatorDescription\": \"Utilice este separador en la lista aleatoria.\",\n    \"outputLengthDescription\": \"Generar esta cantidad de elementos aleatorios\",\n    \"resultTitle\": \"Lista barajada\",\n    \"shortDescription\": \"Aleatorizar el orden de los elementos de la lista\",\n    \"shuffledListLength\": \"Longitud de la lista aleatoria\",\n    \"shuffledListSeparator\": \"Separador de lista aleatoria\",\n    \"title\": \"Barajar\"\n  },\n  \"sort\": {\n    \"caseSensitive\": \"Ordenación sensible a mayúsculas y minúsculas\",\n    \"caseSensitiveDescription\": \"Ordena los elementos en mayúsculas y minúsculas por separado. Las mayúsculas preceden a las minúsculas en una lista ascendente. (Solo funciona en orden alfabético).\",\n    \"description\": \"La utilidad de navegador más sencilla del mundo para ordenar elementos de listas. Introduce tu lista y especifica los criterios de ordenación para organizar los elementos en orden ascendente o descendente. Perfecta para organizar datos, procesar texto o crear listas ordenadas.\",\n    \"inputItemSeparator\": \"Separador de elementos de entrada\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"joinSeparatorDescription\": \"Utilice este símbolo como conector entre elementos de una lista ordenada.\",\n    \"orderDescription\": \"Seleccione un orden de clasificación.\",\n    \"orderOptions\": {\n      \"decreasing\": \"Orden decreciente\",\n      \"increasing\": \"Orden creciente\"\n    },\n    \"removeDuplicates\": \"Eliminar duplicados\",\n    \"removeDuplicatesDescription\": \"Eliminar elementos de lista duplicados.\",\n    \"resultTitle\": \"Lista ordenada\",\n    \"shortDescription\": \"Ordenar los elementos de la lista en el orden especificado\",\n    \"sortMethod\": \"Método de ordenación\",\n    \"sortMethodDescription\": \"Seleccione un método de clasificación.\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Ordenar alfabéticamente\",\n      \"length\": \"Ordenar por longitud\",\n      \"numeric\": \"Ordenar numéricamente\"\n    },\n    \"sortedItemProperties\": \"Propiedades de elementos ordenados\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Delimitar los elementos de la lista de entrada con una expresión regular.\",\n        \"title\": \"Utilice una expresión regular para dividir\"\n      },\n      \"symbol\": {\n        \"description\": \"Delimitar los elementos de la lista de entrada con un carácter.\",\n        \"title\": \"Utilice un símbolo para dividir\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Establezca un símbolo delimitador o una expresión regular.\",\n    \"title\": \"Clasificar\"\n  },\n  \"truncate\": {\n    \"description\": \"La utilidad de navegador más sencilla del mundo para truncar listas. Introduce tu lista y especifica el número máximo de elementos que quieres conservar. Perfecta para procesar datos, gestionar listas o limitar la longitud del contenido.\",\n    \"shortDescription\": \"Truncar la lista a un número especificado de elementos\",\n    \"title\": \"Truncar\"\n  },\n  \"unwrap\": {\n    \"description\": \"La utilidad basada en navegador más sencilla del mundo para descomprimir elementos de listas. Ingrese su lista descomprimida y especifique los criterios de descompresión para aplanar los elementos organizados. Perfecta para procesar datos, manipular texto o extraer contenido de listas estructuradas.\",\n    \"shortDescription\": \"Desplegar elementos de lista del formato estructurado\",\n    \"title\": \"Desenvolver\"\n  },\n  \"wrap\": {\n    \"description\": \"Agregue texto antes y después de cada elemento de la lista.\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"joinSeparatorDescription\": \"Separador para unir la lista envuelta\",\n    \"leftTextDescription\": \"Texto para agregar antes de cada elemento\",\n    \"removeEmptyItems\": \"Eliminar elementos vacíos\",\n    \"resultTitle\": \"Lista envuelta\",\n    \"rightTextDescription\": \"Texto para agregar después de cada elemento\",\n    \"shortDescription\": \"Envolver elementos de la lista con criterios específicos\",\n    \"splitByRegex\": \"Dividir por expresión regular\",\n    \"splitBySymbol\": \"Dividir por símbolo\",\n    \"splitOptions\": \"Opciones divididas\",\n    \"splitSeparatorDescription\": \"Separador para dividir la lista\",\n    \"title\": \"Envoltura\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite agregar texto antes y después de cada elemento de una lista. Puede especificar texto diferente para los lados izquierdo y derecho, y controlar cómo se procesa la lista. Es útil para agregar comillas, corchetes u otro formato a los elementos de la lista, preparar datos para diferentes formatos o crear texto estructurado.\",\n      \"title\": \"Envoltura de listas\"\n    },\n    \"wrapOptions\": \"Opciones de envoltura\"\n  }\n}\n"
  },
  {
    "path": "public/locales/es/number.json",
    "content": "{\n  \"arithmeticSequence\": {\n    \"commonDifferenceDescription\": \"Diferencia común entre términos (d)\",\n    \"description\": \"Genere secuencias aritméticas con parámetros personalizables.\",\n    \"firstTermDescription\": \"Primer término de la secuencia (a₁)\",\n    \"numberOfTermsDescription\": \"Número de términos a generar (n)\",\n    \"outputFormat\": \"Formato de salida\",\n    \"resultTitle\": \"Secuencia generada\",\n    \"separatorDescription\": \"Separador entre términos\",\n    \"sequenceParameters\": \"Parámetros de secuencia\",\n    \"shortDescription\": \"Generar secuencias aritméticas\",\n    \"title\": \"Sucesión aritmética\",\n    \"toolInfo\": {\n      \"description\": \"Una sucesión aritmética es una secuencia de números donde la diferencia entre cada término consecutivo es constante. Esta diferencia constante se denomina diferencia común. Dados el primer término (a₁) y la diferencia común (d), cada término se puede hallar sumando la diferencia común al término anterior.\",\n      \"title\": \"¿Qué es una secuencia aritmética?\"\n    }\n  },\n  \"generate\": {\n    \"arithmeticSequenceOption\": \"Opción de secuencia aritmética\",\n    \"description\": \"Genere una secuencia de números con parámetros personalizables.\",\n    \"numberOfElementsDescription\": \"Número de elementos en secuencia.\",\n    \"resultTitle\": \"Números generados\",\n    \"separator\": \"Separador\",\n    \"separatorDescription\": \"Separa elementos de la secuencia aritmética mediante este carácter.\",\n    \"shortDescription\": \"Generar números aleatorios en rangos específicos\",\n    \"startSequenceDescription\": \"Inicie la secuencia desde este número.\",\n    \"stepDescription\": \"Aumente cada elemento en esta cantidad\",\n    \"title\": \"Generar\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite generar una secuencia de números con parámetros personalizables. Se puede especificar el valor inicial, el tamaño del paso y el número de elementos.\",\n      \"title\": \"Generar números\"\n    }\n  },\n  \"ohmsLaw\": {\n    \"description\": \"Calcula voltaje, corriente y resistencia.\",\n    \"longDescription\": \"Esta calculadora aplica la Ley de Ohm (V = I × R) para determinar cualquiera de los tres parámetros eléctricos cuando se conocen los otros dos. La Ley de Ohm es un principio fundamental en ingeniería eléctrica que describe la relación entre el voltaje (V), la corriente (I) y la resistencia (R). Esta herramienta es esencial para aficionados a la electrónica, ingenieros eléctricos y estudiantes que trabajan con circuitos para resolver rápidamente valores desconocidos en sus diseños eléctricos.\",\n    \"shortDescription\": \"Calcular voltaje, corriente o resistencia en circuitos eléctricos utilizando la Ley de Ohm\",\n    \"title\": \"Ley de Ohm\"\n  },\n  \"randomNumberGenerator\": {\n    \"description\": \"Genere números aleatorios dentro de un rango específico con opciones personalizables.\",\n    \"error\": {\n      \"generationFailed\": \"No se pudieron generar números aleatorios. Por favor, revise sus parámetros de entrada.\"\n    },\n    \"info\": {\n      \"description\": \"Un generador de números aleatorios crea números impredecibles dentro de un rango específico. Esta herramienta utiliza generación de números aleatorios criptográficamente segura para garantizar resultados verdaderamente aleatorios. Útil para simulaciones, juegos, muestreo estadístico y escenarios de prueba.\",\n      \"title\": \"¿Qué es un generador de números aleatorios?\"\n    },\n    \"longDescription\": \"Genera números aleatorios dentro de un rango específico con opciones para enteros o decimales, permitiendo o evitando duplicados y ordenando resultados. Ideal para simulaciones, pruebas, juegos y análisis estadístico.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDecimals\": {\n          \"description\": \"Generar números decimales en lugar de enteros\",\n          \"title\": \"Permitir números decimales\"\n        },\n        \"allowDuplicates\": {\n          \"description\": \"Permitir que el mismo número aparezca varias veces\",\n          \"title\": \"Permitir duplicados\"\n        },\n        \"countDescription\": \"Número de números aleatorios a generar (1-10 000)\",\n        \"sortResults\": {\n          \"description\": \"Ordena los números generados en orden ascendente\",\n          \"title\": \"Ordenar resultados\"\n        },\n        \"title\": \"Opciones de generación\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Caracteres para separar los números generados\",\n        \"title\": \"Configuración de salida\"\n      },\n      \"range\": {\n        \"maxDescription\": \"Valor máximo (inclusive)\",\n        \"minDescription\": \"Valor mínimo (inclusive)\",\n        \"title\": \"Ajustes de rango\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Contar\",\n      \"hasDuplicates\": \"Contiene duplicados\",\n      \"isSorted\": \"Ordenado\",\n      \"range\": \"Rango\",\n      \"title\": \"Números aleatorios generados\"\n    },\n    \"shortDescription\": \"Generar números aleatorios en rangos personalizados\",\n    \"title\": \"Generador de números aleatorios\"\n  },\n  \"randomPortGenerator\": {\n    \"description\": \"Genere puertos de red aleatorios dentro de rangos específicos con opciones personalizables.\",\n    \"error\": {\n      \"generationFailed\": \"No se pudieron generar puertos aleatorios. Por favor, revise sus parámetros de entrada.\"\n    },\n    \"info\": {\n      \"description\": \"Un generador de puertos aleatorio crea números de puerto de red impredecibles dentro de rangos específicos. Esta herramienta cumple con los estándares de números de puerto de la IANA e incluye la identificación de servicios comunes. Resulta útil para desarrollo, pruebas, configuración de red y para evitar conflictos de puertos.\",\n      \"title\": \"¿Qué es un generador de puertos aleatorios?\"\n    },\n    \"longDescription\": \"Genera puertos de red aleatorios dentro de rangos específicos (conocidos, registrados, dinámicos o personalizados). Ideal para desarrollo, pruebas y configuración de red. Incluye identificación de servicios de puerto para puertos comunes.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDuplicates\": {\n          \"description\": \"Permitir que el mismo puerto aparezca varias veces\",\n          \"title\": \"Permitir duplicados\"\n        },\n        \"countDescription\": \"Número de puertos aleatorios a generar (1-1000)\",\n        \"sortResults\": {\n          \"description\": \"Ordenar los puertos generados en orden ascendente\",\n          \"title\": \"Ordenar resultados\"\n        },\n        \"title\": \"Opciones de generación\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Caracteres para separar los puertos generados\",\n        \"title\": \"Configuración de salida\"\n      },\n      \"range\": {\n        \"custom\": \"Gama personalizada\",\n        \"dynamic\": \"Puertos dinámicos (49152-65535)\",\n        \"maxPortDescription\": \"Número máximo de puerto (1-65535)\",\n        \"minPortDescription\": \"Número de puerto mínimo (1-65535)\",\n        \"registered\": \"Puertos registrados (1024-49151)\",\n        \"title\": \"Configuración del rango de puertos\",\n        \"wellKnown\": \"Puertos conocidos (1-1023)\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Contar\",\n      \"hasDuplicates\": \"Contiene duplicados\",\n      \"isSorted\": \"Ordenado\",\n      \"portDetails\": \"Detalles del puerto\",\n      \"range\": \"Rango de puerto\",\n      \"title\": \"Puertos aleatorios generados\"\n    },\n    \"shortDescription\": \"Generar puertos de red aleatorios\",\n    \"title\": \"Generador de puertos aleatorios\"\n  },\n  \"slackline\": {\n    \"description\": \"Calcula la tensión en una slackline\",\n    \"longDescription\": \"Esta calculadora asume una carga en el centro de la cuerda.\",\n    \"shortDescription\": \"Calcula la tensión aproximada de un slackline o tendedero. No confíes en esto para tu seguridad.\",\n    \"title\": \"Tensión de la slackline\"\n  },\n  \"sphereArea\": {\n    \"description\": \"Área de una esfera\",\n    \"longDescription\": \"Esta calculadora determina el área superficial de una esfera mediante la fórmula A = 4πr². Puedes introducir el radio para hallar el área superficial o introducir el área superficial para calcular el radio requerido. Esta herramienta es útil para estudiantes de geometría, ingenieros que trabajan con objetos esféricos y cualquier persona que necesite realizar cálculos con superficies esféricas.\",\n    \"shortDescription\": \"Calcular el área superficial de una esfera en función de su radio.\",\n    \"title\": \"Área de una esfera\"\n  },\n  \"sphereVolume\": {\n    \"description\": \"Volumen de una esfera\",\n    \"longDescription\": \"Esta calculadora calcula el volumen de una esfera mediante la fórmula V = (4/3)πr³. Puede introducir el radio o el diámetro para calcular el volumen, o bien introducir el volumen para determinar el radio requerido. Esta herramienta es útil para estudiantes, ingenieros y profesionales que trabajan con objetos esféricos en campos como la física, la ingeniería y la fabricación.\",\n    \"shortDescription\": \"Calcular el volumen de una esfera usando el radio o el diámetro.\",\n    \"title\": \"Volumen de una esfera\"\n  },\n  \"sum\": {\n    \"description\": \"Calcula la suma de una lista de números. Introduce los números separados por comas o saltos de línea para obtener la suma total.\",\n    \"example1Description\": \"En este ejemplo, calculamos la suma de diez enteros positivos. Estos enteros se muestran en una columna y su suma total es 19494.\",\n    \"example1Title\": \"Suma de diez números positivos\",\n    \"example2Description\": \"Este ejemplo invierte una columna de veintitrés sílabas e imprime todas las palabras de abajo a arriba. Para separar los elementos de la lista, utiliza el carácter \\\\n como separador de elementos de entrada, lo que significa que cada elemento ocupa una línea independiente.\",\n    \"example2Title\": \"Contar árboles en el parque\",\n    \"example3Description\": \"En este ejemplo, sumamos noventa valores diferentes: números positivos, negativos, enteros y fracciones decimales. Usamos una coma como separador de entrada y, tras sumar todos los valores, obtenemos 0 como resultado.\",\n    \"example3Title\": \"Suma de números enteros y decimales\",\n    \"example4Description\": \"En este ejemplo, calculamos la suma de los diez dígitos y activamos la opción \\\"Imprimir suma continua\\\". Obtenemos los valores intermedios de la suma durante la adición. Por lo tanto, obtenemos la siguiente secuencia en la salida: 0, 1 (0 + 1), 3 (0 + 1 + 2), 6 (0 + 1 + 2 + 3), 10 (0 + 1 + 2 + 3 + 4), y así sucesivamente.\",\n    \"example4Title\": \"Suma continua de números\",\n    \"extractionTypes\": {\n      \"delimiter\": {\n        \"description\": \"Personaliza aquí el separador de números (predeterminado: un salto de línea).\",\n        \"title\": \"Delimitador de números\"\n      },\n      \"smart\": {\n        \"description\": \"Detectar automáticamente números en la entrada.\",\n        \"title\": \"Suma inteligente\"\n      }\n    },\n    \"inputTitle\": \"Aporte\",\n    \"numberExtraction\": \"Extracción de números\",\n    \"printRunningSum\": \"Imprimir suma continua\",\n    \"printRunningSumDescription\": \"Muestra la suma a medida que se calcula paso a paso.\",\n    \"resultTitle\": \"Total\",\n    \"runningSum\": \"Suma continua\",\n    \"shortDescription\": \"Calcular la suma de números\",\n    \"title\": \"Suma\",\n    \"toolInfo\": {\n      \"description\": \"Esta es una utilidad en línea para calcular la suma de un conjunto de números. Puede introducir los números separados por coma, espacio o cualquier otro carácter, incluido el salto de línea. También puede pegar un fragmento de texto que contenga valores numéricos que desee sumar y la utilidad los extraerá y calculará su suma.\",\n      \"title\": \"¿Qué es una calculadora de suma de números?\"\n    }\n  },\n  \"voltageDropInWire\": {\n    \"description\": \"Calcula el voltaje de ida y vuelta y la pérdida de potencia en un cable de 2 conductores.\",\n    \"longDescription\": \"Esta calculadora ayuda a determinar la caída de tensión y la pérdida de potencia en un cable eléctrico de dos conductores. Considera la longitud del cable, el calibre del cable (área de la sección transversal), la resistividad del material y el flujo de corriente. La herramienta calcula la caída de tensión de ida y vuelta, la resistencia total del cable y la potencia disipada en forma de calor. Resulta especialmente útil para ingenieros eléctricos, electricistas y aficionados al diseñar sistemas eléctricos para garantizar que los niveles de tensión se mantengan dentro de los límites aceptables en la carga.\",\n    \"shortDescription\": \"Calcular la caída de tensión y la pérdida de potencia en cables eléctricos en función de la longitud, el material y la corriente.\",\n    \"title\": \"Caída de tensión de ida y vuelta en el cable\"\n  }\n}\n"
  },
  {
    "path": "public/locales/es/pdf.json",
    "content": "{\n  \"compressPdf\": {\n    \"compressedFileSize\": \"Tamaño de archivo comprimido\",\n    \"compressingPdf\": \"Comprimiendo PDF...\",\n    \"compressionLevel\": \"Nivel de compresión\",\n    \"compressionSettings\": \"Configuración de compresión\",\n    \"description\": \"Reduzca el tamaño de los archivos PDF manteniendo la calidad usando Ghostscript\",\n    \"errorCompressingPdf\": \"Error al comprimir el PDF: {{error}}\",\n    \"errorReadingPdf\": \"No se pudo leer el archivo PDF. Asegúrese de que sea válido.\",\n    \"fileSize\": \"Tamaño del archivo original\",\n    \"highCompression\": \"Alta compresión\",\n    \"highCompressionDescription\": \"Reducción máxima del tamaño del archivo con cierta pérdida de calidad\",\n    \"inputTitle\": \"PDF de entrada\",\n    \"longDescription\": \"Comprime archivos PDF de forma segura en tu navegador con Ghostscript. Tus archivos nunca saldrán de tu dispositivo, lo que garantiza una privacidad total y reduce su tamaño para compartirlos por correo electrónico, subirlos a sitios web o ahorrar espacio de almacenamiento. Con tecnología WebAssembly.\",\n    \"lowCompression\": \"Baja compresión\",\n    \"lowCompressionDescription\": \"Reduce ligeramente el tamaño del archivo con una pérdida mínima de calidad\",\n    \"mediumCompression\": \"Compresión media\",\n    \"mediumCompressionDescription\": \"Equilibrio entre el tamaño del archivo y la calidad\",\n    \"pages\": \"Número de páginas\",\n    \"resultTitle\": \"PDF comprimido\",\n    \"shortDescription\": \"Comprime archivos PDF de forma segura en tu navegador\",\n    \"title\": \"Comprimir PDF\"\n  },\n  \"editor\": {\n    \"description\": \"Editor de PDF avanzado con funciones de anotación, rellenado de formularios, resaltado y exportación. Edite sus PDF directamente en el navegador con herramientas profesionales que incluyen inserción de texto, dibujo, resaltado, firma y rellenado de formularios.\",\n    \"shortDescription\": \"Edite archivos PDF con herramientas avanzadas de anotación, firma y edición\",\n    \"title\": \"Editor de PDF\"\n  },\n  \"merge\": {\n    \"inputTitle\": \"PDF de entrada\",\n    \"loadingText\": \"Extrayendo páginas\",\n    \"resultTitle\": \"Salida PDF fusionada\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite fusionar varios archivos PDF en un solo documento. Para usarla, simplemente cargue los archivos PDF que desea fusionar. La herramienta combinará todas las páginas de los archivos de entrada en un solo documento PDF.\",\n      \"title\": \"¿Cómo utilizar la herramienta Combinar PDF?\"\n    }\n  },\n  \"mergePdf\": {\n    \"description\": \"Combine varios archivos PDF en un solo documento.\",\n    \"inputTitle\": \"Entrada de archivos PDF\",\n    \"mergingPdfs\": \"Fusionar archivos PDF\",\n    \"pdfOptions\": \"Opciones de PDF\",\n    \"resultTitle\": \"PDF fusionado\",\n    \"shortDescription\": \"Fusionar varios archivos PDF en un solo documento\",\n    \"sortByFileName\": \"Ordenar por nombre de archivo\",\n    \"sortByFileNameDescription\": \"Ordenar archivos PDF alfabéticamente por nombre de archivo\",\n    \"sortByUploadOrder\": \"Ordenar por orden de carga\",\n    \"sortByUploadOrderDescription\": \"Mantenga los archivos PDF en el orden en que se cargaron\",\n    \"title\": \"Fusionar PDF\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite combinar varios archivos PDF en un solo documento. Puedes elegir cómo ordenar los PDF y la herramienta los combinará en el orden especificado.\",\n      \"title\": \"Fusionar archivos PDF\"\n    }\n  },\n  \"pdfToEpub\": {\n    \"description\": \"Transforme documentos PDF en archivos EPUB para una mejor compatibilidad con los lectores electrónicos.\",\n    \"shortDescription\": \"Convertir archivos PDF al formato EPUB\",\n    \"title\": \"PDF a EPUB\"\n  },\n  \"pdfToPng\": {\n    \"description\": \"Transforme documentos PDF en paneles PNG.\",\n    \"longDescription\": \"Sube un PDF y convierte cada página en una imagen PNG de alta calidad directamente en tu navegador. Esta herramienta es ideal para extraer contenido visual o compartir páginas individuales. No se cargan datos: todo se ejecuta localmente.\",\n    \"shortDescription\": \"Convertir PDF a imágenes PNG\",\n    \"title\": \"PDF a PNG\"\n  },\n  \"convertToPdf\": {\n    \"title\": \"Imágenes a PDF\",\n    \"description\": \"Convertir varios formatos de imagen (PNG, GIF, JPG, TIF, PSD, SVG, WEBP, HEIC, RAW) a PDF, con opciones para escalar la imagen y elegir la orientación de la página.\",\n    \"shortDescription\": \"Convertir imágenes a PDF con control de escala y orientación\"\n  },\n  \"protectPdf\": {\n    \"description\": \"Agregue protección con contraseña a sus archivos PDF de forma segura en su navegador\",\n    \"shortDescription\": \"Proteger con contraseña los archivos PDF de forma segura\",\n    \"title\": \"Proteger PDF\"\n  },\n  \"rotatePdf\": {\n    \"allPagesWillBeRotated\": \"Todo {{count}} Las páginas se rotarán\",\n    \"angleOptions\": {\n      \"clockwise90\": \"90° en sentido horario\",\n      \"counterClockwise270\": \"270° (90° en sentido antihorario)\",\n      \"upsideDown180\": \"180° (al revés)\"\n    },\n    \"applyToAllPages\": \"Aplicar a todas las páginas\",\n    \"description\": \"Rotar páginas en un documento PDF.\",\n    \"inputTitle\": \"PDF de entrada\",\n    \"longDescription\": \"Cambie la orientación de las páginas PDF girándolas 90, 180 o 270 grados. Útil para corregir documentos escaneados incorrectamente o preparar archivos PDF para imprimir.\",\n    \"pageRangesDescription\": \"Introduzca números de página o rangos separados por comas (por ejemplo, 1, 3, 5-7)\",\n    \"pageRangesPlaceholder\": \"p. ej., 1,5-8\",\n    \"pagesWillBeRotated\": \"{{count}} página{{count !== 1 ? 's' : ''}} se rotará\",\n    \"pdfPageCount\": \"PDF tiene {{count}} página{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"PDF rotado\",\n    \"rotatingPages\": \"Páginas rotatorias\",\n    \"rotationAngle\": \"Ángulo de rotación\",\n    \"rotationSettings\": \"Configuración de rotación\",\n    \"shortDescription\": \"Girar páginas en un documento PDF\",\n    \"title\": \"Girar PDF\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite rotar páginas en un documento PDF. Puede rotar todas las páginas o especificar páginas individuales. Elija un ángulo de rotación: 90° en sentido horario, 180° (invertido) o 270° (90° en sentido antihorario). Para rotar páginas específicas, desmarque la opción \\\"Aplicar a todas las páginas\\\" e introduzca los números de página o intervalos separados por comas (p. ej., 1, 3, 5-7).\",\n      \"title\": \"Cómo utilizar la herramienta Rotar PDF\"\n    }\n  },\n  \"splitPdf\": {\n    \"description\": \"Extraer páginas específicas de un documento PDF.\",\n    \"extractingPages\": \"Extrayendo páginas\",\n    \"inputTitle\": \"PDF de entrada\",\n    \"pageExtractionPreview\": \"{{count}} página{{count !== 1 ? 's' : ''}} se extraerán\",\n    \"pageRangesDescription\": \"Introduzca números de página o rangos separados por comas (por ejemplo, 1, 3, 5-7)\",\n    \"pageRangesPlaceholder\": \"p. ej., 1,5-8\",\n    \"pageSelection\": \"Selección de página\",\n    \"pdfPageCount\": \"PDF tiene {{count}} página{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"PDF extraído\",\n    \"shortDescription\": \"Extraer páginas específicas de un archivo PDF\",\n    \"title\": \"PDF dividido\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite extraer páginas específicas de un documento PDF. Puede especificar páginas individuales o un rango de páginas para extraer.\",\n      \"title\": \"PDF dividido\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/es/string.json",
    "content": "{\n  \"base64\": {\n    \"decode\": \"Descodificación Base64\",\n    \"description\": \"Codificar o decodificar texto utilizando la codificación Base64.\",\n    \"encode\": \"Codificación Base64\",\n    \"inputTitle\": \"Datos de entrada\",\n    \"optionsTitle\": \"Opciones de Base64\",\n    \"resultTitle\": \"Resultado\",\n    \"shortDescription\": \"Codificar o decodificar datos utilizando Base64.\",\n    \"title\": \"Codificador/decodificador Base64\",\n    \"toolInfo\": {\n      \"description\": \"Base64 es un esquema de codificación que representa datos en formato de cadena ASCII traduciéndolos a una representación de base 64. Si bien puede usarse para codificar cadenas, se usa comúnmente para codificar datos binarios para su transmisión a través de medios diseñados para procesar datos textuales.\",\n      \"title\": \"¿Qué es Base64?\"\n    }\n  },\n  \"censor\": {\n    \"description\": \"Utilidad para censurar palabras en texto. Cargue su texto en el formulario de entrada de la izquierda, especifique todas las palabras incorrectas en las opciones y obtendrá el texto censurado al instante en el área de salida. longDescription: 'Con esta herramienta en línea, puede censurar ciertas palabras en cualquier texto. Puede especificar una lista de palabras no deseadas (como palabras malsonantes o palabras secretas) y el programa las reemplazará con palabras alternativas y creará un texto seguro. Las palabras se pueden especificar en un campo de texto de varias líneas en las opciones, introduciendo una palabra por línea.', keywords: ['text', 'censor', 'words', 'characters'], component: lazy(() => import('./index')), i18n: { name: 'string:censor.title', description: 'string:censor.description'\",\n    \"shortDescription\": \"Enmascare rápidamente las malas palabras o reemplácelas con palabras alternativas.\",\n    \"title\": \"Censor de texto\"\n  },\n  \"createPalindrome\": {\n    \"description\": \"La utilidad de navegador más sencilla del mundo para crear palíndromos a partir de cualquier texto. Introduce texto y transfórmalo al instante en un palíndromo que se lee igual de derecho a revés. Perfecto para juegos de palabras, crear patrones de texto simétricos o explorar curiosidades lingüísticas.\",\n    \"shortDescription\": \"Crea texto que se lea igual hacia adelante y hacia atrás\",\n    \"title\": \"Crear palíndromo\"\n  },\n  \"extractSubstring\": {\n    \"description\": \"La utilidad basada en navegador más sencilla del mundo para extraer subcadenas de texto. Introduce el texto y especifica las posiciones inicial y final para extraer la parte deseada. Perfecta para el procesamiento de datos, el análisis de texto o la extracción de contenido específico de bloques de texto más grandes.\",\n    \"shortDescription\": \"Extraer una porción de texto entre posiciones específicas\",\n    \"title\": \"Extraer subcadena\"\n  },\n  \"hiddenCharacterDetector\": {\n    \"analysisOptions\": \"Opciones de análisis\",\n    \"category\": \"Categoría\",\n    \"description\": \"Detecta caracteres Unicode ocultos, especialmente caracteres RTL Override que podrían usarse en ataques.\",\n    \"foundChars\": \"Encontró {{count}} personaje(s) oculto(s):\",\n    \"inputPlaceholder\": \"Introduzca texto para comprobar si hay caracteres ocultos...\",\n    \"inputTitle\": \"Texto para analizar\",\n    \"invisibleChar\": \"Personaje invisible\",\n    \"invisibleFound\": \"Personajes invisibles encontrados\",\n    \"longDescription\": \"Esta herramienta le ayuda a detectar caracteres Unicode ocultos en el texto, especialmente caracteres de anulación de derecha a izquierda (RTL), que pueden usarse en ataques. Puede identificar caracteres invisibles, caracteres de ancho cero y otras secuencias Unicode potencialmente maliciosas que podrían estar ocultas en texto aparentemente inocente.\",\n    \"noHiddenChars\": \"No se detectaron caracteres ocultos en el texto.\",\n    \"optionsDescription\": \"Configure qué tipos de caracteres ocultos detectar y cómo mostrar los resultados.\",\n    \"position\": \"Posición\",\n    \"rtlAlert\": \"¡Se detectaron caracteres de anulación RTL! Este texto puede contener caracteres ocultos maliciosos.\",\n    \"rtlFound\": \"Se encontró una anulación de RTL\",\n    \"rtlOverride\": \"Carácter de anulación RTL\",\n    \"rtlWarning\": \"ADVERTENCIA: Se detectaron caracteres de anulación RTL. Podría usarse en ataques.\",\n    \"shortDescription\": \"Encuentra caracteres Unicode ocultos en el texto\",\n    \"summary\": \"Resumen del análisis\",\n    \"title\": \"Detector de caracteres ocultos\",\n    \"totalChars\": \"Total de personajes ocultos: {{count}}\",\n    \"unicode\": \"Unicode\",\n    \"zeroWidthChar\": \"Carácter de ancho cero\",\n    \"zeroWidthFound\": \"Se encontraron caracteres de ancho cero\"\n  },\n  \"join\": {\n    \"blankLinesAndTrailingSpaces\": \"Líneas en blanco y espacios finales\",\n    \"deleteBlankDescription\": \"Eliminar líneas que no tengan símbolos de texto.\",\n    \"deleteBlankTitle\": \"Eliminar líneas en blanco\",\n    \"deleteTrailingDescription\": \"Eliminar espacios y tabulaciones al final de las líneas.\",\n    \"deleteTrailingTitle\": \"Eliminar espacios finales\",\n    \"description\": \"Une fragmentos de texto con separadores personalizables.\",\n    \"inputTitle\": \"Fragmentos de texto\",\n    \"joinCharacterDescription\": \"Símbolo que conecta fragmentos de texto. (Espacio por defecto).\",\n    \"joinCharacterPlaceholder\": \"Unirse al personaje\",\n    \"resultTitle\": \"Texto unido\",\n    \"shortDescription\": \"Unir elementos de texto con un separador especificado\",\n    \"textMergedOptions\": \"Opciones de texto fusionado\",\n    \"title\": \"Unirse al texto\",\n    \"toolInfo\": {\n      \"description\": \"Con esta herramienta puedes unir partes del texto. Toma una lista de valores de texto, separados por saltos de línea, y los fusiona. Puedes configurar el carácter que se colocará entre las partes del texto combinado. También puedes ignorar todas las líneas vacías y eliminar los espacios y tabulaciones al final de todas las líneas. ¡Textabulento!\",\n      \"title\": \"¿Qué es un ensamblador de texto?\"\n    }\n  },\n  \"palindrome\": {\n    \"description\": \"La utilidad de navegador más sencilla del mundo para comprobar si un texto es un palíndromo. Comprueba al instante si tu texto se lee igual de adelante hacia atrás. Ideal para juegos de palabras, análisis lingüísticos o la validación de patrones de texto simétricos. Admite varios delimitadores y detección de palíndromos de varias palabras.\",\n    \"shortDescription\": \"Comprueba si el texto se lee igual hacia adelante y hacia atrás\",\n    \"title\": \"Palíndromo\"\n  },\n  \"passwordGenerator\": {\n    \"avoidAmbiguous\": \"Evite caracteres ambiguos (i, I, l, 0, O)\",\n    \"description\": \"Genere contraseñas aleatorias seguras con longitud y tipos de caracteres personalizables. Elija entre minúsculas, mayúsculas, números y caracteres especiales. Opción para evitar caracteres ambiguos para una mejor legibilidad.\",\n    \"includeLowercase\": \"Incluir letras minúsculas (a-z)\",\n    \"includeNumbers\": \"Incluir números (0-9)\",\n    \"includeSymbols\": \"Incluir caracteres especiales\",\n    \"includeUppercase\": \"Incluir letras mayúsculas (A-Z)\",\n    \"lengthDesc\": \"Longitud de la contraseña\",\n    \"lengthPlaceholder\": \"p. ej. 12\",\n    \"optionsTitle\": \"Opciones de contraseña\",\n    \"resultTitle\": \"Contraseña generada\",\n    \"shortDescription\": \"Genere contraseñas aleatorias seguras con opciones personalizadas\",\n    \"title\": \"Generador de contraseñas\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta genera contraseñas aleatorias seguras según los criterios que selecciones. Puedes personalizar la longitud, incluir o excluir diferentes tipos de caracteres y evitar caracteres ambiguos para una mejor legibilidad. Perfecta para crear contraseñas seguras para cuentas, aplicaciones o cualquier necesidad de seguridad.\",\n      \"title\": \"Acerca del generador de contraseñas\"\n    }\n  },\n  \"quote\": {\n    \"allowDoubleQuotation\": \"Permitir comillas dobles\",\n    \"description\": \"Agregue comillas alrededor del texto con opciones personalizables.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"leftQuoteDescription\": \"Carácter(es) de comillas izquierdas\",\n    \"processAsMultiLine\": \"Procesar como texto de varias líneas\",\n    \"quoteEmptyLines\": \"Citar líneas vacías\",\n    \"quoteOptions\": \"Opciones de cotización\",\n    \"resultTitle\": \"Texto citado\",\n    \"rightQuoteDescription\": \"Carácter(es) de comillas derechas\",\n    \"shortDescription\": \"Agregue comillas alrededor del texto con varios estilos\",\n    \"title\": \"Citador de texto\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite añadir comillas al texto. Permite elegir diferentes caracteres de comillas, gestionar texto multilínea y controlar el procesamiento de líneas vacías. Resulta útil para preparar texto para programación, formatear datos o crear texto estilizado.\",\n      \"title\": \"Citador de texto\"\n    }\n  },\n  \"randomizeCase\": {\n    \"description\": \"La utilidad de navegador más sencilla del mundo para aleatorizar mayúsculas y minúsculas. Introduce tu texto y transfórmalo al instante con mayúsculas y minúsculas aleatorias. Perfecta para crear efectos de texto únicos, comprobar la distinción entre mayúsculas y minúsculas o generar patrones de texto variados.\",\n    \"shortDescription\": \"Aleatorizar el caso de las letras en el texto\",\n    \"title\": \"Aleatorizar caso\"\n  },\n  \"removeDuplicateLines\": {\n    \"description\": \"Carga tu texto en el formulario de entrada de la izquierda y obtendrás texto al instante sin líneas duplicadas en el área de salida. Potente, gratuito y rápido. Carga líneas de texto: obtén líneas de texto únicas.\",\n    \"shortDescription\": \"Eliminar rápidamente todas las líneas repetidas del texto\",\n    \"title\": \"Eliminar líneas duplicadas\"\n  },\n  \"repeat\": {\n    \"delimiterDescription\": \"Delimitador para copias de salida.\",\n    \"delimiterPlaceholder\": \"Delimitador\",\n    \"description\": \"Repita el texto varias veces con separadores personalizables.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"numberPlaceholder\": \"Número\",\n    \"repeatAmountDescription\": \"Número de repeticiones.\",\n    \"repetitionsDelimiter\": \"Delimitador de repeticiones\",\n    \"resultTitle\": \"Texto repetido\",\n    \"shortDescription\": \"Repetir el texto varias veces\",\n    \"textRepetitions\": \"Repeticiones de texto\",\n    \"title\": \"Repetir texto\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta le permite repetir un texto determinado varias veces con un separador opcional.\",\n      \"title\": \"Repetir texto\"\n    }\n  },\n  \"reverse\": {\n    \"description\": \"La utilidad de navegador más sencilla del mundo para invertir texto. Introduce cualquier texto y revísalo al instante, carácter por carácter. Perfecta para crear texto espejo, analizar palíndromos o experimentar con patrones de texto. Conserva los espacios y caracteres especiales al invertir.\",\n    \"inputTitle\": \"Texto para revertir\",\n    \"processMultiLine\": \"Procesar texto de varias líneas\",\n    \"processMultiLineDescription\": \"Cada línea se invertirá independientemente\",\n    \"resultTitle\": \"Texto invertido\",\n    \"reversalOptions\": \"Opciones de reversión\",\n    \"shortDescription\": \"Invertir cualquier texto carácter por carácter\",\n    \"skipEmptyLines\": \"Saltar líneas vacías\",\n    \"skipEmptyLinesDescription\": \"Las líneas vacías se eliminarán de la salida.\",\n    \"title\": \"Contrarrestar\",\n    \"trimWhitespace\": \"Recortar espacios en blanco\",\n    \"trimWhitespaceDescription\": \"Eliminar los espacios iniciales y finales de cada línea\"\n  },\n  \"rot13\": {\n    \"description\": \"Codificar o decodificar texto utilizando el cifrado ROT13.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"resultTitle\": \"Resultado de ROT13\",\n    \"shortDescription\": \"Codificar o decodificar texto utilizando el cifrado ROT13.\",\n    \"title\": \"Codificador/decodificador ROT13\",\n    \"toolInfo\": {\n      \"description\": \"ROT13 (rotación de 13 posiciones) es un cifrado simple de sustitución de letras que reemplaza una letra por la decimotercera letra siguiente en el alfabeto. ROT13 es un caso especial del cifrado César, desarrollado en la antigua Roma. Dado que el alfabeto inglés tiene 26 letras, ROT13 es su propio inverso; es decir, para deshacer ROT13, se aplica el mismo algoritmo, por lo que se puede usar la misma acción para codificar y decodificar.\",\n      \"title\": \"¿Qué es ROT13?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"Girar caracteres en el texto en posiciones específicas.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"processAsMultiLine\": \"Procesar como texto de varias líneas (rotar cada línea por separado)\",\n    \"resultTitle\": \"Texto rotado\",\n    \"rotateLeft\": \"Girar a la izquierda\",\n    \"rotateRight\": \"Girar a la derecha\",\n    \"rotationOptions\": \"Opciones de rotación\",\n    \"shortDescription\": \"Desplazar caracteres en el texto por posición.\",\n    \"stepDescription\": \"Número de posiciones a rotar\",\n    \"title\": \"Girar texto\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite rotar caracteres en una cadena un número específico de posiciones. Puede rotar a la izquierda o a la derecha, y procesar texto de varias líneas rotando cada línea por separado. La rotación de cadenas es útil para transformaciones de texto simples, la creación de patrones o la implementación de técnicas básicas de cifrado.\",\n      \"title\": \"Rotación de cuerdas\"\n    }\n  },\n  \"split\": {\n    \"charAfterChunkDescription\": \"Carácter después de cada fragmento\",\n    \"charBeforeChunkDescription\": \"Carácter antes de cada fragmento\",\n    \"chunksDescription\": \"Número de fragmentos de igual longitud en la salida.\",\n    \"chunksTitle\": \"Utilice varios fragmentos\",\n    \"description\": \"La utilidad para navegador más sencilla del mundo para dividir texto. Introduce tu texto y especifica un separador para dividirlo en varias partes. Ideal para procesar datos, manipular texto o extraer contenido específico de bloques de texto más grandes.\",\n    \"lengthDescription\": \"Número de símbolos que se colocarán en cada fragmento de salida.\",\n    \"lengthTitle\": \"Utilice la longitud para dividir\",\n    \"outputSeparatorDescription\": \"Carácter que se colocará entre los fragmentos divididos. (Por defecto, es el salto de línea \\\"\\\\n\\\").\",\n    \"outputSeparatorOptions\": \"Opciones de separador de salida\",\n    \"regexDescription\": \"Expresión regular que se usará para dividir el texto en partes. (Múltiples espacios por defecto).\",\n    \"regexTitle\": \"Utilice una expresión regular para dividir\",\n    \"resultTitle\": \"Fragmentos de texto\",\n    \"shortDescription\": \"Dividir el texto en varias partes usando un separador\",\n    \"splitSeparatorOptions\": \"Opciones de separador dividido\",\n    \"symbolDescription\": \"Carácter que se utilizará para dividir el texto en partes. (Espacio por defecto).\",\n    \"symbolTitle\": \"Utilice un símbolo para dividir\",\n    \"title\": \"Dividir\"\n  },\n  \"statistic\": {\n    \"characterFrequencyAnalysis\": \"Análisis de frecuencia de caracteres\",\n    \"characterFrequencyAnalysisDescription\": \"Cuenta con qué frecuencia aparece cada carácter en el texto.\",\n    \"delimitersOptions\": \"Opciones de delimitadores\",\n    \"description\": \"Analizar texto y generar estadísticas completas.\",\n    \"includeEmptyLines\": \"Incluir líneas vacías\",\n    \"includeEmptyLinesDescription\": \"Incluya líneas en blanco al contar líneas\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"resultTitle\": \"Estadísticas de texto\",\n    \"sentenceDelimitersDescription\": \"Introduzca caracteres personalizados utilizados para delimitar oraciones en su idioma (separados por coma) o déjelo en blanco como valor predeterminado.\",\n    \"sentenceDelimitersPlaceholder\": \"p.ej. ., !, ?, ...\",\n    \"shortDescription\": \"Obtenga estadísticas sobre su texto\",\n    \"statisticsOptions\": \"Opciones de estadísticas\",\n    \"title\": \"Estadísticas de texto\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta le permite analizar texto y generar estadísticas completas que incluyen recuento de caracteres, recuento de palabras, recuento de líneas y análisis de frecuencia de caracteres y palabras.\",\n      \"title\": \"¿Qué es un? {{title}}?\"\n    },\n    \"wordDelimitersDescription\": \"Ingrese una expresión regular personalizada para contar palabras o déjela en blanco para el valor predeterminado.\",\n    \"wordDelimitersPlaceholder\": \"p. ej. \\\\s.,;:!?\\\"«»()…\",\n    \"wordFrequencyAnalysis\": \"Análisis de frecuencia de palabras\",\n    \"wordFrequencyAnalysisDescription\": \"Cuenta con qué frecuencia aparece cada palabra en el texto.\"\n  },\n  \"textReplacer\": {\n    \"description\": \"Reemplazar patrones de texto con contenido nuevo.\",\n    \"findPatternInText\": \"Encuentra este patrón en el texto\",\n    \"findPatternUsingRegexp\": \"Encontrar un patrón usando una expresión regular\",\n    \"inputTitle\": \"Texto a reemplazar\",\n    \"newTextPlaceholder\": \"Nuevo texto\",\n    \"regexpDescription\": \"Introduzca la expresión regular que desea reemplazar.\",\n    \"replacePatternDescription\": \"Introduzca el patrón que se utilizará para el reemplazo.\",\n    \"replaceText\": \"Reemplazar texto\",\n    \"resultTitle\": \"Texto con reemplazos\",\n    \"searchPatternDescription\": \"Introduzca el patrón de texto que desea reemplazar.\",\n    \"searchText\": \"Buscar texto\",\n    \"shortDescription\": \"Reemplace rápidamente el texto en su contenido\",\n    \"title\": \"Reemplazador de texto\",\n    \"toolInfo\": {\n      \"description\": \"Reemplace fácilmente texto específico en su contenido con esta sencilla herramienta basada en navegador. Simplemente ingrese el texto, configure el texto que desea reemplazar y el valor de reemplazo, y obtenga la versión actualizada al instante.\",\n      \"title\": \"Reemplazador de texto\"\n    }\n  },\n  \"toMorse\": {\n    \"dashSymbolDescription\": \"Símbolo que corresponderá al guión en código Morse.\",\n    \"description\": \"Convertir texto a código Morse.\",\n    \"dotSymbolDescription\": \"Símbolo que corresponderá al punto en código Morse.\",\n    \"longSignal\": \"Señal larga\",\n    \"resultTitle\": \"Código Morse\",\n    \"shortDescription\": \"Codifique rápidamente texto en morse\",\n    \"shortSignal\": \"Señal corta\",\n    \"title\": \"Cadena a morse\"\n  },\n  \"truncate\": {\n    \"addTruncationIndicator\": \"Agregar indicador de truncamiento\",\n    \"charactersPlaceholder\": \"Personajes\",\n    \"description\": \"Acortar el texto a una longitud específica.\",\n    \"indicatorDescription\": \"Caracteres que se añaden al final (o al principio) del texto. Nota: Se consideran para la longitud.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"leftSideDescription\": \"Eliminar caracteres del inicio del texto.\",\n    \"leftSideTruncation\": \"Truncamiento del lado izquierdo\",\n    \"lengthAndLines\": \"Longitud y líneas\",\n    \"lineByLineDescription\": \"Truncar cada línea por separado.\",\n    \"lineByLineTruncating\": \"Truncamiento línea por línea\",\n    \"maxLengthDescription\": \"Número de caracteres a dejar en el texto.\",\n    \"numberPlaceholder\": \"Número\",\n    \"resultTitle\": \"Texto truncado\",\n    \"rightSideDescription\": \"Eliminar caracteres del final del texto.\",\n    \"rightSideTruncation\": \"Truncamiento del lado derecho\",\n    \"shortDescription\": \"Truncar texto a una longitud especificada\",\n    \"suffixAndAffix\": \"Sufijo y afijo\",\n    \"title\": \"Truncar texto\",\n    \"toolInfo\": {\n      \"description\": \"Cargue su texto en el formulario de entrada de la izquierda y obtendrá automáticamente el texto truncado a la derecha.\",\n      \"title\": \"Truncar texto\"\n    },\n    \"truncationSide\": \"Lado de truncamiento\"\n  },\n  \"uppercase\": {\n    \"description\": \"Convertir texto a letras mayúsculas.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"resultTitle\": \"Texto en mayúsculas\",\n    \"shortDescription\": \"Convertir texto a mayúsculas\",\n    \"title\": \"Convertir a mayúsculas\"\n  },\n  \"urlDecode\": {\n    \"inputTitle\": \"Cadena de entrada (URL escapada)\",\n    \"resultTitle\": \"Cadena de salida\",\n    \"toolInfo\": {\n      \"description\": \"Cargue su cadena y automáticamente obtendrá la URL sin escape.\",\n      \"longDescription\": \"Esta herramienta decodifica una cadena previamente codificada. La decodificación es la operación inversa de la codificación. Todos los caracteres con porcentaje se decodifican en caracteres comprensibles. Algunos de los valores más conocidos son %20 para un espacio, %3a para dos puntos, %2f para una barra diagonal y %3f para un signo de interrogación. Los dos dígitos después del signo de porcentaje son los valores del código char en hexadecimal.\",\n      \"shortDescription\": \"Eliminar rápidamente el escape de una URL de una cadena.\",\n      \"title\": \"Decodificador de URL de cadena\"\n    }\n  },\n  \"urlEncode\": {\n    \"encodingOption\": {\n      \"nonSpecialCharDescription\": \"Si se selecciona, todos los caracteres en la cadena de entrada se convertirán a codificación URL (no solo especial).\",\n      \"nonSpecialCharPlaceholder\": \"Codificar caracteres no especiales\",\n      \"title\": \"Opciones de codificación\"\n    },\n    \"inputTitle\": \"Cadena de entrada\",\n    \"resultTitle\": \"Cadena con URL escapada\",\n    \"toolInfo\": {\n      \"description\": \"Cargue su cadena y se escapará automáticamente mediante URL.\",\n      \"longDescription\": \"Esta herramienta codifica una cadena en URL. Los caracteres especiales de URL se convierten a codificación de porcentaje. Esta codificación se denomina codificación porcentual porque el valor numérico de cada carácter se convierte en un signo de porcentaje seguido de un valor hexadecimal de dos dígitos. Los valores hexadecimales se determinan en función del valor del punto de código del carácter. Por ejemplo, un espacio se escapa a %20, los dos puntos a %3a y una barra a %2f. Los caracteres que no son especiales se mantienen sin cambios. Si también necesita convertir caracteres no especiales a codificación de porcentaje, hemos añadido una opción adicional que le permite hacerlo. Seleccione la opción \\\"codificar caracteres no especiales\\\" para habilitar esta función.\",\n      \"shortDescription\": \"Escapar rápidamente una URL de una cadena.\",\n      \"title\": \"Codificador de URL de cadena\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/es/time.json",
    "content": "{\n  \"checkLeapYears\": {\n    \"description\": \"Compruebe si un año es bisiesto y obtenga información sobre el año bisiesto.\",\n    \"exampleDescription\": \"Una amiga nuestra nació en un año bisiesto, el 29 de febrero, y por eso solo cumple años cada cuatro años. Como nunca recordamos su cumpleaños, usamos nuestro programa para crear una lista de recordatorios de los próximos años bisiestos. Para crear una lista de sus próximos cumpleaños, cargamos una secuencia de años del 2025 al 2040 en la entrada y obtenemos el estado de cada año. Si el programa indica que es un año bisiesto, sabemos que nos invitarán a una fiesta de cumpleaños el 29 de febrero.\",\n    \"exampleTitle\": \"Encuentra cumpleaños el 29 de febrero\",\n    \"inputTitle\": \"Año de entrada\",\n    \"resultTitle\": \"Resultado del año bisiesto\",\n    \"shortDescription\": \"Comprobar si un año es bisiesto\",\n    \"title\": \"Consultar años bisiestos\",\n    \"toolInfo\": {\n      \"description\": \"Un año bisiesto es un año que contiene un día adicional (29 de febrero) para mantener la sincronización del año calendario con el año astronómico. Los años bisiestos ocurren cada 4 años, excepto los años divisibles por 100, pero no por 400.\",\n      \"title\": \"¿Qué es un año bisiesto?\"\n    }\n  },\n  \"convertDaysToHours\": {\n    \"addHoursName\": \"Agregar nombre de horas\",\n    \"addHoursNameDescription\": \"Añade la cadena horas a los valores de salida\",\n    \"description\": \"Convierta días en horas con opciones personalizables.\",\n    \"hoursName\": \"Horas Nombre\",\n    \"shortDescription\": \"Convertir días a horas\",\n    \"title\": \"Convertir días a horas\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite convertir días a horas. Puede introducir los días como números o con unidades, y la herramienta los convertirá a horas. También puede añadir el sufijo \\\"horas\\\" a los valores de salida.\",\n      \"title\": \"Convertir días a horas\"\n    }\n  },\n  \"convertHoursToDays\": {\n    \"addDaysName\": \"Agregar nombre de días\",\n    \"addDaysNameDescription\": \"Añade la cadena días a los valores de salida\",\n    \"daysName\": \"Nombre de los días\",\n    \"description\": \"Convierte horas en días con opciones personalizables.\",\n    \"shortDescription\": \"Convertir horas a días\",\n    \"title\": \"Convertir horas a días\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite convertir horas a días. Puede introducir las horas como números o con unidades, y la herramienta las convertirá a días. También puede añadir el sufijo \\\"días\\\" a los valores de salida.\",\n      \"title\": \"Convertir horas a días\"\n    }\n  },\n  \"convertSecondsToTime\": {\n    \"addPadding\": \"Añadir relleno\",\n    \"addPaddingDescription\": \"Añade relleno de ceros a las horas, minutos y segundos.\",\n    \"description\": \"Convierte los segundos a un formato de hora legible (horas:minutos:segundos). Introduce la cantidad de segundos para obtener la hora formateada.\",\n    \"shortDescription\": \"Convertir segundos a formato de hora\",\n    \"timePadding\": \"Relleno de tiempo\",\n    \"title\": \"Convertir segundos a tiempo\",\n    \"toolInfo\": {\n      \"title\": \"¿Qué es un? {{title}}?\"\n    }\n  },\n  \"convertTimeToSeconds\": {\n    \"description\": \"Convierte la hora formateada (HH:MM:SS) a segundos.\",\n    \"inputTitle\": \"Hora de entrada\",\n    \"resultTitle\": \"Artículos de segunda clase\",\n    \"shortDescription\": \"Convertir formato de hora a segundos\",\n    \"title\": \"Convertir tiempo a segundos\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite convertir cadenas de tiempo formateadas (HH:MM:SS) a segundos. Resulta útil para calcular duraciones e intervalos de tiempo.\",\n      \"title\": \"Convertir tiempo a segundos\"\n    }\n  },\n  \"convertUnixToDate\": {\n    \"addUtcLabel\": \"Añadir el sufijo 'UTC'\",\n    \"addUtcLabelDescription\": \"Mostrar 'UTC' después de la fecha convertida (solo para el modo UTC)\",\n    \"description\": \"Convierte una marca de tiempo de Unix en una fecha legible para humanos.\",\n    \"outputOptions\": \"Opciones de salida\",\n    \"shortDescription\": \"Convertir la marca de tiempo de Unix a fecha\",\n    \"title\": \"Convertir Unix a fecha\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta convierte una marca de tiempo Unix (en segundos) a un formato de fecha legible (p. ej., AAAA-MM-DD HH:MM:SS). Admite salida local y UTC, lo que la hace útil para interpretar rápidamente marcas de tiempo de registros, API o sistemas que usan hora Unix.\",\n      \"title\": \"Convertir Unix a fecha\"\n    },\n    \"useLocalTime\": \"Utilice la hora local\",\n    \"useLocalTimeDescription\": \"Mostrar la fecha convertida en su zona horaria local en lugar de UTC\",\n    \"withLabel\": \"Opciones\"\n  },\n  \"crontabGuru\": {\n    \"description\": \"Generar y comprender expresiones cron. Crear programaciones cron para tareas automatizadas y trabajos del sistema.\",\n    \"shortDescription\": \"Generar y comprender expresiones cron\",\n    \"title\": \"Gurú de Crontab\"\n  },\n  \"timeBetweenDates\": {\n    \"description\": \"Calcula la diferencia horaria entre dos fechas. Obtén la duración exacta en días, horas, minutos y segundos.\",\n    \"endDate\": \"Fecha de finalización\",\n    \"endDateTime\": \"Fecha y hora de finalización\",\n    \"endTime\": \"Fin de los tiempos\",\n    \"endTimezone\": \"Zona horaria final\",\n    \"shortDescription\": \"Calcular el tiempo entre dos fechas\",\n    \"startDate\": \"Fecha de inicio\",\n    \"startDateTime\": \"Fecha y hora de inicio\",\n    \"startTime\": \"Hora de inicio\",\n    \"startTimezone\": \"Zona horaria de inicio\",\n    \"title\": \"Tiempo entre fechas\",\n    \"toolInfo\": {\n      \"description\": \"Calcula la diferencia horaria exacta entre dos fechas y horas, compatible con diferentes zonas horarias. Esta herramienta proporciona un desglose detallado de la diferencia horaria en varias unidades (años, meses, días, horas, minutos y segundos).\",\n      \"title\": \"Calculadora de tiempo entre fechas\"\n    }\n  },\n  \"truncateClockTime\": {\n    \"description\": \"Trunca la hora del reloj para quitar segundos o minutos. Redondea la hora a la hora, minuto o intervalo personalizado más cercano.\",\n    \"printDroppedComponents\": \"Imprimir componentes descartados\",\n    \"shortDescription\": \"Truncar el tiempo del reloj a la precisión especificada\",\n    \"timePadding\": \"Relleno de tiempo\",\n    \"title\": \"Truncar el tiempo del reloj\",\n    \"toolInfo\": {\n      \"title\": \"¿Qué es un? {{title}}?\"\n    },\n    \"truncateMinutesAndSeconds\": \"Truncar minutos y segundos\",\n    \"truncateMinutesAndSecondsDescription\": \"Elimina ambos componentes: los minutos y los segundos de cada hora del reloj.\",\n    \"truncateOnlySeconds\": \"Truncar solo segundos\",\n    \"truncateOnlySecondsDescription\": \"Elimina el componente de segundos de cada hora del reloj.\",\n    \"truncationSide\": \"Lado de truncamiento\",\n    \"useZeroPadding\": \"Utilice relleno de ceros\",\n    \"zeroPaddingDescription\": \"Haga que todos los componentes de tiempo siempre tengan dos dígitos de ancho.\",\n    \"zeroPrintDescription\": \"Muestra las partes descartadas como valores cero \\\"00\\\".\",\n    \"zeroPrintTruncatedParts\": \"Piezas truncadas de impresión cero\"\n  }\n}\n"
  },
  {
    "path": "public/locales/es/translation.json",
    "content": "{\n  \"audio\": {\n    \"changeSpeed\": {\n      \"description\": \"Cambia la velocidad de reproducción de los archivos de audio. Acelera o ralentiza el audio manteniendo el tono.\",\n      \"name\": \"Cambiar la velocidad del audio\",\n      \"shortDescription\": \"Cambiar la velocidad de los archivos de audio\"\n    },\n    \"extractAudio\": {\n      \"description\": \"Extraiga la pista de audio de un archivo de vídeo y guárdelo como un archivo de audio separado en el formato elegido (AAC, MP3, WAV).\",\n      \"name\": \"Extraer audio\",\n      \"shortDescription\": \"Extrae audio de archivos de vídeo (MP4, MOV, etc.) a AAC, MP3 o WAV.\"\n    }\n  },\n  \"baseFileInput\": {\n    \"copyFailed\": \"No se pudo copiar: {{error}}\",\n    \"dropFileHere\": \"Deja tu {{type}} aquí\",\n    \"fileCopied\": \"Archivo copiado\",\n    \"selectFileDescription\": \"Haga clic aquí para seleccionar uno {{type}} Desde su dispositivo, presione Ctrl+V para usar un {{type}} desde su portapapeles, o arrastre y suelte un archivo desde el escritorio\"\n  },\n  \"categories\": {\n    \"audio\": {\n      \"description\": \"Herramientas para trabajar con audio: extraer audio de vídeo, ajustar la velocidad del audio, fusionar varios archivos de audio y mucho más.\",\n      \"title\": \"Herramientas de audio\"\n    },\n    \"csv\": {\n      \"description\": \"Herramientas para trabajar con archivos CSV: convierta CSV a diferentes formatos, manipule datos CSV, valide la estructura CSV y procese archivos CSV de manera eficiente.\",\n      \"title\": \"Herramientas CSV\"\n    },\n    \"gif\": {\n      \"description\": \"Herramientas para trabajar con animaciones GIF: cree GIF transparentes, extraiga marcos GIF, agregue texto a GIF, recorte, rote, invierta GIF y mucho más.\",\n      \"title\": \"Herramientas GIF\"\n    },\n    \"image-generic\": {\n      \"description\": \"Herramientas para trabajar con imágenes: comprimir, redimensionar, recortar, convertir a JPG, rotar, eliminar fondo y mucho más.\",\n      \"title\": \"Herramientas de imagen\"\n    },\n    \"json\": {\n      \"description\": \"Herramientas para trabajar con estructuras de datos JSON: embellecer y minimizar objetos JSON, aplanar matrices JSON, convertir valores JSON en cadenas, analizar datos y mucho más\",\n      \"title\": \"Herramientas JSON\"\n    },\n    \"list\": {\n      \"description\": \"Herramientas para trabajar con listas: ordenar, invertir, aleatorizar listas, encontrar elementos de listas únicos y duplicados, cambiar separadores de elementos de listas y mucho más.\",\n      \"title\": \"Herramientas de lista\"\n    },\n    \"number\": {\n      \"description\": \"Herramientas para trabajar con números: generar secuencias numéricas, convertir números en palabras y palabras en números, ordenar, redondear, factorizar números y mucho más.\",\n      \"title\": \"Herramientas numéricas\"\n    },\n    \"pdf\": {\n      \"description\": \"Herramientas para trabajar con archivos PDF: extraer texto de archivos PDF, convertir archivos PDF a otros formatos, manipular archivos PDF y mucho más.\",\n      \"title\": \"Herramientas PDF\"\n    },\n    \"png\": {\n      \"description\": \"Herramientas para trabajar con imágenes PNG: convierta PNG a JPG, cree PNG transparentes, cambie colores PNG, recorte, rote, cambie el tamaño de PNG y mucho más.\",\n      \"title\": \"Herramientas PNG\"\n    },\n    \"seeAll\": \"Ver todo {{title}}\",\n    \"string\": {\n      \"description\": \"Herramientas para trabajar con texto: convertir texto en imágenes, buscar y reemplazar texto, dividir texto en fragmentos, unir líneas de texto, repetir texto y mucho más.\",\n      \"title\": \"Herramientas de texto\"\n    },\n    \"time\": {\n      \"description\": \"Herramientas para trabajar con hora y fecha: calcular diferencias horarias, convertir entre zonas horarias, formatear fechas, generar secuencias de fechas y mucho más.\",\n      \"title\": \"Herramientas de tiempo\"\n    },\n    \"try\": \"Intentar {{title}}\",\n    \"video\": {\n      \"description\": \"Herramientas para trabajar con vídeos: extrae fotogramas de vídeos, crea GIF a partir de vídeos, convierte vídeos a diferentes formatos y mucho más.\",\n      \"title\": \"Herramientas de vídeo\"\n    },\n    \"xml\": {\n      \"description\": \"Herramientas para trabajar con estructuras de datos XML: visor, embellecedor, validador y mucho más\",\n      \"title\": \"Herramientas XML\"\n    }\n  },\n  \"csv\": {\n    \"findIncompleteCsvRecords\": {\n      \"description\": \"Simplemente cargue su archivo CSV en el formulario a continuación y esta herramienta comprobará automáticamente si ninguna fila o columna tiene valores faltantes. En las opciones de la herramienta, puede ajustar el formato del archivo de entrada (especifique el delimitador, las comillas y el carácter de comentario). Además, puede activar la comprobación de valores vacíos, omitir líneas vacías y limitar el número de mensajes de error en la salida.\",\n      \"name\": \"Encontrar registros CSV incompletos\",\n      \"shortDescription\": \"Encuentre rápidamente filas y columnas en CSV que tengan valores faltantes.\"\n    }\n  },\n  \"hero\": {\n    \"brand\": \"OmniTools\",\n    \"description\": \"Aumente su productividad con OmniTools, ¡el kit de herramientas definitivo para agilizar su trabajo! Acceda a miles de utilidades intuitivas para editar imágenes, texto, listas y datos, todo directamente desde su navegador.\",\n    \"examples\": {\n      \"calculateNumberSum\": \"Calcular la suma de números\",\n      \"changeGifSpeed\": \"Cambiar la velocidad del GIF\",\n      \"compressPng\": \"Comprimir PNG\",\n      \"createTransparentImage\": \"Crear una imagen transparente\",\n      \"prettifyJson\": \"Embellecer JSON\",\n      \"sortList\": \"Ordenar una lista\",\n      \"splitPdf\": \"PDF dividido\",\n      \"splitText\": \"Dividir un texto\",\n      \"trimVideo\": \"Recortar vídeo\"\n    },\n    \"searchPlaceholder\": \"Buscar en todas las herramientas\",\n    \"title\": \"Haga las cosas rápidamente con\"\n  },\n  \"inputFooter\": {\n    \"clear\": \"Claro\",\n    \"copyToClipboard\": \"Copiar al portapapeles\",\n    \"importFromFile\": \"Importar desde archivo\"\n  },\n  \"list\": {\n    \"group\": {\n      \"description\": \"La utilidad basada en navegador más sencilla del mundo para agrupar elementos de listas. Introduce tu lista y especifica los criterios de agrupación para organizar los elementos en grupos lógicos. Perfecta para categorizar datos, organizar información o crear listas estructuradas. Admite separadores personalizados y diversas opciones de agrupación.\",\n      \"name\": \"Grupo\",\n      \"shortDescription\": \"Agrupar elementos de la lista por propiedades comunes\"\n    },\n    \"reverse\": {\n      \"description\": \"Esta sencilla aplicación basada en navegador imprime todos los elementos de la lista en orden inverso. Los elementos de entrada se pueden separar con cualquier símbolo y también se puede cambiar el separador de los elementos de la lista invertidos.\",\n      \"name\": \"Contrarrestar\",\n      \"shortDescription\": \"Invertir una lista rápidamente\"\n    },\n    \"sort\": {\n      \"description\": \"Esta es una aplicación súper sencilla basada en navegador que ordena los elementos de una lista en orden creciente o decreciente. Puedes ordenar los elementos alfabéticamente, numéricamente o por longitud. También puedes eliminar elementos duplicados y vacíos, así como recortar elementos individuales con espacios en blanco. Puedes usar cualquier carácter separador para separar los elementos de la lista de entrada o, alternativamente, usar una expresión regular. Además, puedes crear un nuevo delimitador para la lista de salida ordenada.\",\n      \"name\": \"Clasificar\",\n      \"shortDescription\": \"Ordenar rápidamente una lista\"\n    }\n  },\n  \"navbar\": {\n    \"buyMeACoffee\": \"Invítame a un café\",\n    \"home\": \"Hogar\",\n    \"tools\": \"Herramientas\",\n    \"hireMe\": \"Contrátame\"\n  },\n  \"number\": {\n    \"generate\": {\n      \"description\": \"Calcule rápidamente una lista de enteros en su navegador. Para obtener la lista, simplemente especifique el primer entero, el valor de cambio y el recuento total en las opciones a continuación, y esta utilidad generará esa cantidad de enteros.\",\n      \"name\": \"Generar números\",\n      \"shortDescription\": \"Calcula rápidamente una lista de números enteros en tu navegador\"\n    },\n    \"sum\": {\n      \"description\": \"Esta es una aplicación súper sencilla para navegador que suma números. Los números ingresados se pueden separar con cualquier símbolo y también se puede cambiar el separador de los números sumados.\",\n      \"name\": \"Sumar números\",\n      \"shortDescription\": \"Sumar rápidamente una lista de números\"\n    }\n  },\n  \"numericInputWithUnit\": {\n    \"unit\": \"Unidad\"\n  },\n  \"pdf\": {\n    \"compressPdf\": {\n      \"description\": \"Reduzca el tamaño de los archivos PDF manteniendo la calidad usando Ghostscript\",\n      \"name\": \"Comprimir PDF\",\n      \"shortDescription\": \"Comprime archivos PDF de forma segura en tu navegador\"\n    },\n    \"mergePdf\": {\n      \"description\": \"Combine varios archivos PDF en un solo documento.\",\n      \"name\": \"Fusionar PDF\",\n      \"shortDescription\": \"Fusionar varios archivos PDF en un solo documento\"\n    },\n    \"pdfToEpub\": {\n      \"description\": \"Transforme documentos PDF en archivos EPUB para una mejor compatibilidad con los lectores electrónicos.\",\n      \"name\": \"PDF a EPUB\",\n      \"shortDescription\": \"Convertir archivos PDF al formato EPUB\"\n    },\n    \"protectPdf\": {\n      \"description\": \"Agregue protección con contraseña a sus archivos PDF de forma segura en su navegador\",\n      \"name\": \"Proteger PDF\",\n      \"shortDescription\": \"Proteger con contraseña los archivos PDF de forma segura\"\n    },\n    \"splitPdf\": {\n      \"description\": \"Extraer páginas específicas de un archivo PDF usando números de página o rangos (por ejemplo, 1,5-8)\",\n      \"name\": \"PDF dividido\",\n      \"shortDescription\": \"Extraer páginas específicas de un archivo PDF\"\n    }\n  },\n  \"resultFooter\": {\n    \"copy\": \"Copiar al portapapeles\",\n    \"download\": \"Descargar\"\n  },\n  \"string\": {\n    \"createPalindrome\": {\n      \"description\": \"La utilidad de navegador más sencilla del mundo para crear palíndromos a partir de cualquier texto. Introduce texto y transfórmalo al instante en un palíndromo que se lee igual de derecho a revés. Perfecto para juegos de palabras, crear patrones de texto simétricos o explorar curiosidades lingüísticas.\",\n      \"name\": \"Crear palíndromo\",\n      \"shortDescription\": \"Crea texto que se lea igual hacia adelante y hacia atrás\"\n    },\n    \"palindrome\": {\n      \"description\": \"La utilidad de navegador más sencilla del mundo para comprobar si un texto es un palíndromo. Comprueba al instante si tu texto se lee igual de adelante hacia atrás. Ideal para juegos de palabras, análisis lingüísticos o la validación de patrones de texto simétricos. Admite varios delimitadores y detección de palíndromos de varias palabras.\",\n      \"name\": \"Palíndromo\",\n      \"shortDescription\": \"Comprueba si el texto se lee igual hacia adelante y hacia atrás\"\n    },\n    \"repeat\": {\n      \"description\": \"Esta herramienta le permite repetir un texto determinado varias veces con un separador opcional.\",\n      \"name\": \"Repetir texto\",\n      \"shortDescription\": \"Repetir el texto varias veces\"\n    },\n    \"reverse\": {\n      \"description\": \"La utilidad de navegador más sencilla del mundo para invertir texto. Introduce cualquier texto y revísalo al instante, carácter por carácter. Perfecta para crear texto espejo, analizar palíndromos o experimentar con patrones de texto. Conserva los espacios y caracteres especiales al invertir.\",\n      \"name\": \"Contrarrestar\",\n      \"shortDescription\": \"Invertir cualquier texto carácter por carácter\"\n    },\n    \"toMorse\": {\n      \"description\": \"La utilidad de navegador más sencilla del mundo para convertir texto a código Morse. Carga tu texto en el formulario de entrada de la izquierda y obtendrás código Morse al instante en el área de salida. Potente, gratuito y rápido. Carga texto y obtén código Morse.\",\n      \"name\": \"Cadena a morse\",\n      \"shortDescription\": \"Codifique rápidamente texto en morse\"\n    },\n    \"uppercase\": {\n      \"description\": \"La utilidad para navegador más sencilla del mundo para convertir texto a mayúsculas. Simplemente introduce tu texto y se convertirá automáticamente a mayúsculas. Ideal para crear titulares, enfatizar texto o estandarizar su formato. Admite varios formatos de texto y conserva caracteres especiales.\",\n      \"name\": \"Mayúsculas\",\n      \"shortDescription\": \"Convertir texto a letras mayúsculas\"\n    }\n  },\n  \"toolExamples\": {\n    \"subtitle\": \"¡Haga clic para probar!\",\n    \"title\": \"{{title}} Ejemplos\"\n  },\n  \"toolFileResult\": {\n    \"copied\": \"Archivo copiado\",\n    \"copyFailed\": \"No se pudo copiar: {{error}}\",\n    \"loading\": \"Cargando... Esto puede tardar un momento.\",\n    \"result\": \"Resultado\"\n  },\n  \"toolHeader\": {\n    \"seeExamples\": \"Ver ejemplos\"\n  },\n  \"toolLayout\": {\n    \"allToolsTitle\": \"Todo {{type}}\"\n  },\n  \"toolMultiFileResult\": {\n    \"copied\": \"Archivo copiado\",\n    \"copyFailed\": \"No se pudo copiar: {{error}}\",\n    \"loading\": \"Cargando... Esto puede tardar un momento.\",\n    \"result\": \"Resultado\"\n  },\n  \"toolMultipleAudioInput\": {\n    \"inputTitle\": \"Aporte {{type}}\",\n    \"noFilesSelected\": \"No hay archivos seleccionados\"\n  },\n  \"toolMultiplePdfInput\": {\n    \"inputTitle\": \"Aporte {{type}}\",\n    \"noFilesSelected\": \"No hay archivos seleccionados\"\n  },\n  \"toolOptions\": {\n    \"title\": \"Opciones de herramientas\"\n  },\n  \"toolTextInput\": {\n    \"copied\": \"Texto copiado\",\n    \"copyFailed\": \"No se pudo copiar: {{error}}\",\n    \"input\": \"Texto de entrada\",\n    \"placeholder\": \"Introduzca su texto aquí...\"\n  },\n  \"toolTextResult\": {\n    \"copied\": \"Texto copiado\",\n    \"copyFailed\": \"No se pudo copiar: {{error}}\",\n    \"loading\": \"Cargando... Esto puede tardar un momento.\",\n    \"result\": \"Resultado\"\n  },\n  \"userTypes\": {\n    \"developers\": \"Desarrolladores\",\n    \"generalUsers\": \"Usuarios generales\"\n  }\n}\n"
  },
  {
    "path": "public/locales/es/video.json",
    "content": "{\n  \"changeSpeed\": {\n    \"defaultMultiplier\": \"Multiplicador predeterminado: 2 significa 2 veces más rápido\",\n    \"description\": \"Cambia la velocidad de reproducción de tus archivos de video. Acelera o ralentiza los videos manteniendo la sincronización del audio. Compatible con varios multiplicadores de velocidad y formatos de video comunes.\",\n    \"inputTitle\": \"Vídeo de entrada\",\n    \"newVideoSpeed\": \"Nueva velocidad de video\",\n    \"resultTitle\": \"Vídeo editado\",\n    \"settingSpeed\": \"Ajuste de velocidad\",\n    \"shortDescription\": \"Cambiar la velocidad de reproducción del vídeo\",\n    \"title\": \"Cambiar la velocidad del video\",\n    \"toolInfo\": {\n      \"title\": \"¿Qué es un? {{title}}?\"\n    }\n  },\n  \"compress\": {\n    \"default\": \"Por defecto\",\n    \"description\": \"Comprime videos escalándolos a diferentes resoluciones como 240p, 480p, 720p, etc. Esta herramienta ayuda a reducir el tamaño del archivo manteniendo una calidad aceptable. Es compatible con formatos de video comunes como MP4, WebM y OGG.\",\n    \"inputTitle\": \"Vídeo de entrada\",\n    \"loadingText\": \"Comprimiendo video...\",\n    \"lossless\": \"Sin pérdida\",\n    \"quality\": \"Calidad (CRF)\",\n    \"resolution\": \"Resolución\",\n    \"resultTitle\": \"Vídeo comprimido\",\n    \"shortDescription\": \"Comprimir vídeos escalando a diferentes resoluciones\",\n    \"title\": \"Comprimir vídeo\",\n    \"worst\": \"El peor\"\n  },\n  \"cropVideo\": {\n    \"cropCoordinates\": \"Coordenadas de cultivo\",\n    \"croppingVideo\": \"Recortar vídeo\",\n    \"description\": \"Recortar el vídeo para eliminar áreas no deseadas.\",\n    \"errorBeyondHeight\": \"El área de recorte se extiende más allá de la altura del video ({{height}}píxeles)\",\n    \"errorBeyondWidth\": \"El área de recorte se extiende más allá del ancho del video ({{width}}píxeles)\",\n    \"errorCroppingVideo\": \"Error al recortar el vídeo. Por favor, revise los parámetros y el archivo de vídeo.\",\n    \"errorLoadingDimensions\": \"No se pudieron cargar las dimensiones del vídeo\",\n    \"errorNonNegativeCoordinates\": \"Las coordenadas X e Y deben ser no negativas\",\n    \"errorPositiveDimensions\": \"El ancho y la altura deben ser positivos\",\n    \"height\": \"Altura\",\n    \"inputTitle\": \"Vídeo de entrada\",\n    \"loadVideoForDimensions\": \"Cargar un vídeo para ver las dimensiones\",\n    \"longDescription\": \"Esta herramienta permite recortar archivos de video para eliminar áreas no deseadas o enfocarse en partes específicas. Es útil para eliminar barras negras, ajustar la relación de aspecto o enfocar el contenido importante. Compatible con varios formatos de video, como MP4, MOV y AVI.\",\n    \"resultTitle\": \"Vídeo recortado\",\n    \"shortDescription\": \"Recortar el vídeo para eliminar áreas no deseadas\",\n    \"title\": \"Recortar vídeo\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite recortar archivos de vídeo para eliminar áreas no deseadas. Puede especificar el área de recorte configurando las coordenadas X e Y, así como las dimensiones de ancho y alto.\",\n      \"title\": \"Recortar vídeo\"\n    },\n    \"videoDimensions\": \"Dimensiones del vídeo: {{width}} × {{height}} píxeles\",\n    \"videoInformation\": \"Información del vídeo\",\n    \"width\": \"Ancho\",\n    \"xCoordinate\": \"X (izquierda)\",\n    \"yCoordinate\": \"Y (arriba)\"\n  },\n  \"flip\": {\n    \"description\": \"Voltee archivos de video horizontal o verticalmente. Refleje videos para efectos especiales o corregir problemas de orientación.\",\n    \"flippingVideo\": \"Vídeo invertido\",\n    \"horizontalLabel\": \"Horizontal (Espejo)\",\n    \"inputTitle\": \"Vídeo de entrada\",\n    \"orientation\": \"Orientación\",\n    \"resultTitle\": \"Vídeo invertido\",\n    \"shortDescription\": \"Voltear el vídeo horizontal o verticalmente\",\n    \"title\": \"Voltear vídeo\",\n    \"verticalLabel\": \"Vertical (al revés)\"\n  },\n  \"gif\": {\n    \"changeSpeed\": {\n      \"description\": \"Cambia la velocidad de reproducción de los GIF. Acelera o ralentiza los GIF manteniendo una animación fluida.\",\n      \"shortDescription\": \"Cambiar la velocidad de la animación GIF\",\n      \"title\": \"Cambiar la velocidad del GIF\"\n    }\n  },\n  \"loop\": {\n    \"description\": \"Crea un vídeo en bucle repitiendo el vídeo original varias veces.\",\n    \"inputTitle\": \"Vídeo de entrada\",\n    \"loopingVideo\": \"Vídeo en bucle\",\n    \"loops\": \"Bucles\",\n    \"numberOfLoops\": \"Número de bucles\",\n    \"resultTitle\": \"Vídeo en bucle\",\n    \"shortDescription\": \"Crear archivos de vídeo en bucle\",\n    \"title\": \"Vídeo en bucle\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite crear un vídeo en bucle repitiendo el vídeo original varias veces. Puedes especificar cuántas veces debe repetirse.\",\n      \"title\": \"¿Qué es un? {{title}}?\"\n    }\n  },\n  \"mergeVideo\": {\n    \"description\": \"Combine varios archivos de vídeo en un vídeo continuo.\",\n    \"longDescription\": \"Esta herramienta te permite fusionar o añadir varios archivos de video en un solo video continuo. Simplemente sube tus videos, organízalos como desees y combínalos en un solo archivo para compartirlos o editarlos fácilmente.\",\n    \"shortDescription\": \"Añade y fusiona vídeos fácilmente.\",\n    \"title\": \"Fusionar vídeos\"\n  },\n  \"rotate\": {\n    \"180Degrees\": \"180° (al revés)\",\n    \"270Degrees\": \"270° (90° en sentido antihorario)\",\n    \"90Degrees\": \"90° en sentido horario\",\n    \"description\": \"Gira archivos de vídeo 90, 180 o 270 grados. Corrige la orientación del vídeo o crea efectos especiales con un control de rotación preciso.\",\n    \"inputTitle\": \"Vídeo de entrada\",\n    \"resultTitle\": \"Vídeo rotado\",\n    \"rotatingVideo\": \"Vídeo giratorio\",\n    \"rotation\": \"Rotación\",\n    \"shortDescription\": \"Girar el vídeo en grados específicos\",\n    \"title\": \"Girar vídeo\"\n  },\n  \"trim\": {\n    \"description\": \"Recorta archivos de vídeo especificando la hora de inicio y la hora de fin. Elimina las secciones no deseadas del principio o del final de los vídeos.\",\n    \"endTime\": \"Fin de los tiempos\",\n    \"inputTitle\": \"Vídeo de entrada\",\n    \"resultTitle\": \"Vídeo recortado\",\n    \"shortDescription\": \"Recortar el vídeo eliminando las secciones no deseadas\",\n    \"startTime\": \"Hora de inicio\",\n    \"timestamps\": \"Marcas de tiempo\",\n    \"title\": \"Recortar vídeo\"\n  },\n  \"videoToGif\": {\n    \"description\": \"Convierte archivos de vídeo a formato GIF animado. Extrae intervalos de tiempo específicos y crea imágenes animadas para compartir.\",\n    \"shortDescription\": \"Convertir vídeo a GIF animado\",\n    \"title\": \"Vídeo a GIF\"\n  }\n}\n"
  },
  {
    "path": "public/locales/es/xml.json",
    "content": "{\n  \"xmlBeautifier\": {\n    \"description\": \"Formatear XML con sangría y espaciado adecuados.\",\n    \"indentation\": \"Sangría\",\n    \"inputTitle\": \"XML de entrada\",\n    \"resultTitle\": \"XML embellecido\",\n    \"shortDescription\": \"Formatear y embellecer el código XML\",\n    \"title\": \"Embellecedor XML\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta le permite formatear datos XML con sangría y espaciado adecuados, haciéndolos más legibles y fáciles de trabajar.\",\n      \"title\": \"Embellecedor XML\"\n    },\n    \"useSpaces\": \"Utilice espacios\",\n    \"useSpacesDescription\": \"Sangrar la salida con espacios\",\n    \"useTabs\": \"Usar pestañas\",\n    \"useTabsDescription\": \"Sangrar la salida con tabulaciones.\"\n  },\n  \"xmlValidator\": {\n    \"description\": \"Validar la sintaxis y estructura XML.\",\n    \"placeholder\": \"Pegue o importe XML aquí...\",\n    \"shortDescription\": \"Validar el código XML para detectar errores\",\n    \"title\": \"Validador XML\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta permite validar la sintaxis y la estructura del XML. Comprueba si el XML está bien formado y proporciona mensajes de error detallados para cualquier problema detectado.\",\n      \"title\": \"Validador XML\"\n    }\n  },\n  \"xmlViewer\": {\n    \"description\": \"Ver y explorar la estructura XML en formato de árbol.\",\n    \"inputTitle\": \"XML de entrada\",\n    \"resultTitle\": \"Vista de árbol XML\",\n    \"title\": \"Visor XML\",\n    \"toolInfo\": {\n      \"description\": \"Esta herramienta le permite ver datos XML en un formato de árbol jerárquico, lo que facilita la exploración y la comprensión de la estructura de los documentos XML.\",\n      \"title\": \"Visor XML\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/audio.json",
    "content": "{\n  \"changeSpeed\": {\n    \"description\": \"Modifier la vitesse de lecture des fichiers audio. Accélérer ou ralentir le son tout en conservant la hauteur.\",\n    \"inputTitle\": \"Entrée audio\",\n    \"newAudioSpeed\": \"Nouvelle vitesse audio\",\n    \"outputFormat\": \"Format de sortie\",\n    \"resultTitle\": \"Audio édité\",\n    \"settingSpeed\": \"Réglage de la vitesse\",\n    \"shortDescription\": \"Modifier la vitesse des fichiers audio\",\n    \"speedDescription\": \"Multiplicateur par défaut : 2 signifie 2x plus rapide\",\n    \"title\": \"Changer la vitesse audio\",\n    \"toolInfo\": {\n      \"title\": \"Qu'est-ce que {{title}}?\"\n    }\n  },\n  \"extractAudio\": {\n    \"description\": \"Extraire la piste audio des fichiers vidéo.\",\n    \"extractingAudio\": \"Extraction audio\",\n    \"inputTitle\": \"Entrée vidéo\",\n    \"outputFormat\": \"Format de sortie\",\n    \"outputFormatDescription\": \"Sélectionnez le format dans lequel l'audio doit être extrait.\",\n    \"resultTitle\": \"Audio extrait\",\n    \"shortDescription\": \"Extrayez l'audio des fichiers vidéo (MP4, MOV, etc.) vers AAC, MP3 ou WAV.\",\n    \"title\": \"Extraire l'audio d'une vidéo\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet d'extraire la piste audio de vos fichiers vidéo. Vous pouvez choisir parmi différents formats audio, notamment AAC, MP3 et WAV.\",\n      \"title\": \"Qu'est-ce que {{title}}?\"\n    }\n  },\n  \"mergeAudio\": {\n    \"description\": \"Combinez plusieurs fichiers audio en un seul fichier audio en les concaténant en séquence.\",\n    \"inputTitle\": \"Fichiers audio d'entrée\",\n    \"longDescription\": \"Cet outil vous permet de fusionner plusieurs fichiers audio en un seul fichier en les concaténant dans l'ordre de leur mise en ligne. Idéal pour combiner des segments de podcast, des morceaux de musique ou tout autre fichier audio à assembler. Compatible avec différents formats audio, dont MP3, AAC et WAV.\",\n    \"mergingAudio\": \"Fusion audio\",\n    \"outputFormat\": \"Format de sortie\",\n    \"resultTitle\": \"Audio fusionné\",\n    \"shortDescription\": \"Fusionnez plusieurs fichiers audio en un seul (MP3, AAC, WAV).\",\n    \"title\": \"Fusionner l'audio\",\n    \"toolInfo\": {\n      \"title\": \"Qu'est-ce que {{title}}?\"\n    }\n  },\n  \"trim\": {\n    \"description\": \"Coupez et rognez des fichiers audio pour extraire des segments spécifiques en spécifiant les heures de début et de fin.\",\n    \"endTime\": \"Fin des temps\",\n    \"endTimeDescription\": \"Heure de fin au format HH:MM:SS (par exemple, 00:01:30)\",\n    \"inputTitle\": \"Entrée audio\",\n    \"longDescription\": \"Cet outil vous permet de découper des fichiers audio en spécifiant les heures de début et de fin. Vous pouvez extraire des segments spécifiques de fichiers audio plus longs, supprimer des parties inutiles ou créer des clips plus courts. Il prend en charge divers formats audio, dont MP3, AAC et WAV. Idéal pour le montage de podcasts, la production musicale ou tout autre besoin d'édition audio.\",\n    \"outputFormat\": \"Format de sortie\",\n    \"resultTitle\": \"Audio coupé\",\n    \"shortDescription\": \"Découpez des fichiers audio pour extraire des segments de temps spécifiques (MP3, AAC, WAV).\",\n    \"startTime\": \"Heure de début\",\n    \"startTimeDescription\": \"Heure de début au format HH:MM:SS (par exemple, 00:00:30)\",\n    \"timeSettings\": \"Paramètres de l'heure\",\n    \"title\": \"Couper le son\",\n    \"toolInfo\": {\n      \"title\": \"Qu'est-ce que {{title}}?\"\n    },\n    \"trimmingAudio\": \"Découpage audio\"\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/converters.json",
    "content": "{\n  \"audioConverter\": {\n    \"title\": \"Convertisseur Audio\",\n    \"description\": \"Convertissez des fichiers audio entre différents formats.\",\n    \"shortDescription\": \"Convertissez des fichiers audio en divers formats.\",\n    \"longDescription\": \"Cet outil vous permet de convertir des fichiers audio d'un format à un autre, en support une large gamme de formats audio pour une conversion transparente.\",\n    \"outputFormat\": \"Format de Sortie\",\n    \"outputFormatDescription\": \"Sélectionnez le format audio de sortie souhaité\",\n    \"inputTitle\": \"Entrée Audio\",\n    \"outputTitle\": \"Audio Converti\"\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/csv.json",
    "content": "{\n  \"changeCsvSeparator\": {\n    \"description\": \"Modifiez le délimiteur/séparateur dans les fichiers CSV. Convertissez vos fichiers entre différents formats CSV, comme la virgule, le point-virgule, la tabulation ou des séparateurs personnalisés.\",\n    \"shortDescription\": \"Modifier le délimiteur du fichier CSV\",\n    \"title\": \"Changer le séparateur CSV\"\n  },\n  \"csvRowsToColumns\": {\n    \"description\": \"Cet outil convertit les lignes d'un fichier CSV (valeurs séparées par des virgules) en colonnes. Il extrait les lignes horizontales du fichier CSV d'entrée une par une, les fait pivoter de 90 degrés et les affiche sous forme de colonnes verticales, l'une après l'autre, séparées par des virgules.', longDescription: 'Cet outil convertit les lignes d'un fichier CSV (valeurs séparées par des virgules) en colonnes. Par exemple, si les données CSV d'entrée comportent 6 lignes, la sortie comportera 6 colonnes et les éléments des lignes seront disposés de haut en bas. Dans un fichier CSV bien formé, le nombre de valeurs par ligne est le même. Cependant, si des lignes contiennent des champs manquants, le programme peut les corriger et vous pouvez choisir parmi les options disponibles : remplir les données manquantes avec des éléments vides ou remplacer les données manquantes par des éléments personnalisés, tels que « manquant », « ? » ou « x ». Pendant le processus de conversion, l'outil nettoie également le fichier CSV des informations inutiles, telles que les lignes vides (c'est-à-dire sans informations visibles) et les commentaires. Pour aider l'outil à identifier correctement les commentaires, vous pouvez spécifier dans les options le symbole au début d'une ligne qui démarre un commentaire. Ce symbole est généralement un dièse « # » ou une double barre oblique « // ». C'est incroyable !\",\n    \"longDescription\": \"Cet outil convertit les lignes d'un fichier CSV (valeurs séparées par des virgules) en colonnes. Par exemple, si les données CSV d'entrée comportent six lignes, le résultat comportera six colonnes et les éléments des lignes seront disposés de haut en bas. Dans un fichier CSV bien formé, le nombre de valeurs par ligne est identique. Cependant, si des champs manquent dans certaines lignes, le programme peut les corriger et vous pouvez choisir parmi les options disponibles : compléter les données manquantes avec des éléments vides ou les remplacer par des éléments personnalisés, tels que\",\n    \"shortDescription\": \"Convertissez les lignes CSV en colonnes.\",\n    \"title\": \"Convertir des lignes CSV en colonnes\"\n  },\n  \"csvToJson\": {\n    \"columnSeparator\": \"Séparateur de colonnes (par exemple, , ; \\\\t)\",\n    \"commentSymbol\": \"Symbole de commentaire (par exemple, #)\",\n    \"conversionOptions\": \"Options de conversion\",\n    \"description\": \"Convertissez des fichiers CSV au format JSON avec des options personnalisables pour les délimiteurs, les guillemets et le formatage de sortie. Prise en charge des en-têtes, des commentaires et de la conversion de type dynamique.\",\n    \"dynamicTypes\": \"Types dynamiques\",\n    \"dynamicTypesDescription\": \"Convertir automatiquement les nombres et les booléens\",\n    \"error\": \"Erreur\",\n    \"errorParsing\": \"Erreur lors de l'analyse du fichier CSV : {{error}}\",\n    \"fieldQuote\": \"Citation de champ (par exemple, \\\")\",\n    \"inputCsvFormat\": \"Format d'entrée CSV\",\n    \"inputTitle\": \"Entrée CSV\",\n    \"invalidCsvFormat\": \"Format CSV non valide\",\n    \"resultTitle\": \"Sortie JSON\",\n    \"shortDescription\": \"Convertissez les données CSV au format JSON.\",\n    \"skipEmptyLines\": \"Sauter les lignes vides\",\n    \"skipEmptyLinesDescription\": \"Ignorer les lignes vides dans le fichier CSV d'entrée\",\n    \"title\": \"Convertir CSV en JSON\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil convertit les fichiers CSV (valeurs séparées par des virgules) en structures de données JSON (JavaScript Object Notation). Il prend en charge différents formats CSV avec des délimiteurs, des guillemets et des symboles de commentaires personnalisables. Le convertisseur peut traiter la première ligne comme un en-tête, ignorer les lignes vides et détecter automatiquement les types de données comme les nombres et les booléens. Le JSON obtenu peut être utilisé pour la migration de données, les sauvegardes ou comme entrée pour d'autres applications.\",\n      \"title\": \"Qu'est-ce qu'un convertisseur CSV en JSON ?\"\n    },\n    \"useHeaders\": \"Utiliser les en-têtes\",\n    \"useHeadersDescription\": \"Traiter la première ligne comme en-tête de colonne\"\n  },\n  \"csvToTsv\": {\n    \"description\": \"Téléchargez votre fichier CSV via le formulaire ci-dessous et il sera automatiquement converti en fichier TSV. Dans les options de l'outil, vous pouvez personnaliser le format CSV d'entrée : spécifiez le délimiteur de champ, le caractère de guillemet et le symbole de commentaire, ignorez les lignes CSV vides et conservez ou non les en-têtes de colonnes CSV.\",\n    \"longDescription\": \"Cet outil convertit les données CSV (valeurs séparées par des virgules) en données TSV (valeurs séparées par des tabulations). CSV et TSV sont des formats de fichier courants pour stocker des données tabulaires, mais ils utilisent des séparateurs différents pour séparer les valeurs : le CSV utilise des virgules (\",\n    \"shortDescription\": \"Convertissez les données CSV au format TSV.\",\n    \"title\": \"Convertir CSV en TSV\"\n  },\n  \"csvToXml\": {\n    \"description\": \"Convertissez des fichiers CSV au format XML avec des options personnalisables.\",\n    \"shortDescription\": \"Convertissez les données CSV au format XML.\",\n    \"title\": \"Convertir CSV en XML\"\n  },\n  \"csvToYaml\": {\n    \"description\": \"Il vous suffit de télécharger votre fichier CSV via le formulaire ci-dessous et il sera automatiquement converti en fichier YAML. Dans les options de l'outil, vous pouvez spécifier le caractère de délimitation de champ, le caractère de guillemet et le caractère de commentaire pour adapter l'outil aux formats CSV personnalisés. Vous pouvez également sélectionner le format YAML de sortie : préservant ou excluant les en-têtes CSV.\",\n    \"longDescription\": \"Cet outil transforme les données CSV (Comma Separated Values) en données YAML (Yet Another Markup Language). CSV est un format tabulaire simple utilisé pour représenter des types de données matricielles composés de lignes et de colonnes. YAML, quant à lui, est un format plus avancé (en fait un sur-ensemble de JSON), qui crée des données plus lisibles pour la sérialisation et prend en charge les listes, les dictionnaires et les objets imbriqués. Ce programme prend en charge différents formats d'entrée CSV : les données d'entrée peuvent être séparées par des virgules (par défaut), des points-virgules, des barres verticales ou utiliser un autre délimiteur. Vous pouvez spécifier le délimiteur exact utilisé par vos données dans les options. De même, dans les options, vous pouvez spécifier le caractère guillemet utilisé pour encadrer les champs CSV (par défaut, un guillemet double). Vous pouvez également ignorer les lignes commençant par des commentaires en spécifiant les symboles de commentaire dans les options. Cela vous permet de préserver la clarté de vos données en ignorant les lignes inutiles. Il existe deux méthodes pour convertir un fichier CSV en YAML. La première méthode convertit chaque ligne CSV en liste YAML. La seconde méthode extrait les en-têtes de la première ligne CSV et crée des objets YAML avec des clés basées sur ces en-têtes. Vous pouvez également personnaliser le format YAML de sortie en spécifiant le nombre d'espaces pour l'indentation des structures YAML. Pour effectuer la conversion inverse, c'est-à-dire convertir un fichier YAML en CSV, vous pouvez utiliser notre outil de conversion YAML en CSV. C'est génial !\",\n    \"shortDescription\": \"Convertissez rapidement un fichier CSV en fichier YAML.\",\n    \"title\": \"Convertir CSV en YAML\"\n  },\n  \"findIncompleteCsvRecords\": {\n    \"checkingOptions\": \"Vérification des options\",\n    \"commentCharacterDescription\": \"Saisissez le caractère indiquant le début d'une ligne de commentaire. Les lignes commençant par ce symbole seront ignorées.\",\n    \"csvInputOptions\": \"Options de saisie CSV\",\n    \"csvSeparatorDescription\": \"Saisissez le caractère utilisé pour délimiter les colonnes dans le fichier d'entrée CSV.\",\n    \"deleteLinesWithNoData\": \"Supprimer les lignes sans données\",\n    \"deleteLinesWithNoDataDescription\": \"Supprimer les lignes vides du fichier d'entrée CSV.\",\n    \"description\": \"Il vous suffit de télécharger votre fichier CSV dans le formulaire ci-dessous et cet outil vérifiera automatiquement qu'aucune ligne ou colonne ne contient de valeur manquante. Dans les options de l'outil, vous pouvez ajuster le format du fichier d'entrée (spécifier le délimiteur, les guillemets et les commentaires). De plus, vous pouvez activer la vérification des valeurs vides, ignorer les lignes vides et limiter le nombre de messages d'erreur dans la sortie.\",\n    \"findEmptyValues\": \"Trouver des valeurs vides\",\n    \"findEmptyValuesDescription\": \"Afficher un message concernant les champs CSV vides (il ne s'agit pas de champs manquants mais de champs qui ne contiennent rien).\",\n    \"inputTitle\": \"Entrée CSV\",\n    \"limitNumberOfMessages\": \"Limiter le nombre de messages\",\n    \"messageLimitDescription\": \"Définissez la limite du nombre de messages dans la sortie.\",\n    \"quoteCharacterDescription\": \"Saisissez le caractère de citation utilisé pour citer les champs de saisie CSV.\",\n    \"resultTitle\": \"Statut CSV\",\n    \"shortDescription\": \"Trouvez rapidement les lignes et les colonnes dans CSV auxquelles il manque des valeurs.\",\n    \"title\": \"Rechercher des enregistrements CSV incomplets\",\n    \"toolInfo\": {\n      \"title\": \"Qu'est-ce qu'un {{title}}?\"\n    }\n  },\n  \"insertCsvColumns\": {\n    \"appendColumns\": \"Ajouter des colonnes\",\n    \"commentCharacterDescription\": \"Saisissez le caractère indiquant le début d'une ligne de commentaire. Les lignes commençant par ce symbole seront ignorées.\",\n    \"csvOptions\": \"Options CSV\",\n    \"csvSeparator\": \"Séparateur CSV\",\n    \"csvToInsert\": \"CSV à insérer\",\n    \"csvToInsertDescription\": \"Saisissez la ou les colonnes à insérer dans le fichier CSV. Le caractère de délimitation des colonnes doit être identique à celui du fichier d'entrée CSV. Remarque : les lignes vides seront ignorées.\",\n    \"customFillDescription\": \"Si le fichier CSV d'entrée est incomplet (valeurs manquantes), ajoutez-vous des champs vides ou des symboles personnalisés aux enregistrements pour créer un CSV bien formé ?\",\n    \"customFillValueDescription\": \"Utilisez cette valeur personnalisée pour remplir les champs manquants. (Fonctionne uniquement avec le mode « Valeurs personnalisées » ci-dessus.)\",\n    \"customPosition\": \"Position personnalisée\",\n    \"customPositionOptionsDescription\": \"Sélectionnez la méthode pour insérer les colonnes dans le fichier CSV.\",\n    \"description\": \"Ajoutez de nouvelles colonnes aux données CSV à des positions spécifiées.\",\n    \"fillWithCustomValues\": \"Remplir avec les valeurs douanières\",\n    \"fillWithEmptyValues\": \"Remplir avec des valeurs vides\",\n    \"headerName\": \"Nom de l'en-tête\",\n    \"headerNameDescription\": \"En-tête de la colonne après laquelle vous souhaitez insérer des colonnes.\",\n    \"inputTitle\": \"Entrée CSV\",\n    \"insertingPositionDescription\": \"Spécifiez où insérer les colonnes dans le fichier CSV.\",\n    \"position\": \"Position\",\n    \"positionOptions\": \"Options de position\",\n    \"prependColumns\": \"Ajouter des colonnes au début\",\n    \"quoteCharDescription\": \"Saisissez le caractère de citation utilisé pour citer les champs de saisie CSV.\",\n    \"resultTitle\": \"Sortie CSV\",\n    \"rowNumberDescription\": \"Numéro de la colonne après laquelle vous souhaitez insérer des colonnes.\",\n    \"separatorDescription\": \"Saisissez le caractère utilisé pour délimiter les colonnes dans le fichier d'entrée CSV.\",\n    \"shortDescription\": \"Insérez rapidement une ou plusieurs nouvelles colonnes n'importe où dans un fichier CSV.\",\n    \"title\": \"Insérer des colonnes CSV\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet d'insérer de nouvelles colonnes dans des données CSV à des emplacements spécifiques. Vous pouvez ajouter des colonnes à des emplacements personnalisés en fonction des noms d'en-tête ou des numéros de colonne.\",\n      \"title\": \"Insérer des colonnes CSV\"\n    }\n  },\n  \"swapCsvColumns\": {\n    \"description\": \"Il vous suffit de télécharger votre fichier CSV dans le formulaire ci-dessous, de spécifier les colonnes à permuter et l'outil modifiera automatiquement la position de ces colonnes dans le fichier de sortie. Dans les options de l'outil, vous pouvez spécifier la position ou le nom des colonnes à permuter, corriger les données incomplètes et éventuellement supprimer les enregistrements vides et commentés.\",\n    \"longDescription\": \"Cet outil réorganise les données CSV en intervertissant la position de leurs colonnes. L'interversion des colonnes améliore la lisibilité d'un fichier CSV en regroupant ou en mettant au premier plan les données fréquemment utilisées, facilitant ainsi leur comparaison et leur modification. Par exemple, vous pouvez intervertir la première colonne avec la dernière, ou la deuxième avec la troisième. Pour intervertir les colonnes en fonction de leur position, sélectionnez l'option\",\n    \"shortDescription\": \"Réorganiser les colonnes CSV.\",\n    \"title\": \"Échanger les colonnes CSV\"\n  },\n  \"transposeCsv\": {\n    \"description\": \"Il vous suffit de télécharger votre fichier CSV via le formulaire ci-dessous et cet outil le transposera automatiquement. Dans les options, vous pouvez spécifier le caractère de début des lignes de commentaire dans le fichier CSV pour les supprimer. De plus, si le fichier CSV est incomplet (valeurs manquantes), vous pouvez remplacer les valeurs manquantes par le caractère vide ou un caractère personnalisé.\",\n    \"longDescription\": \"Cet outil transpose les fichiers CSV (valeurs séparées par des virgules). Il traite le fichier CSV comme une matrice de données et inverse tous les éléments sur la diagonale principale. Le fichier CSV de sortie contient les mêmes données CSV que le fichier d'entrée, mais toutes les lignes sont désormais des colonnes, et toutes les colonnes des lignes. Après la transposition, le fichier CSV aura des dimensions opposées. Par exemple, si le fichier d'entrée comporte 4 colonnes et 3 lignes, le fichier de sortie comportera 3 colonnes et 4 lignes. Lors de la conversion, le programme supprime également les lignes inutiles et corrige les données incomplètes. Plus précisément, l'outil supprime automatiquement tous les enregistrements vides et les commentaires commençant par un caractère spécifique, que vous pouvez définir dans l'option. De plus, en cas de corruption ou de perte des données CSV, l'utilitaire complète le fichier avec des champs vides ou personnalisés, que vous pouvez spécifier dans les options. Csv-abulous !\",\n    \"shortDescription\": \"Transposer rapidement un fichier CSV.\",\n    \"title\": \"Transposer CSV\"\n  },\n  \"tsvToJson\": {\n    \"description\": \"Convertissez les données TSV (valeurs séparées par des tabulations) au format JSON. Transformez les données tabulaires en objets JSON structurés.\",\n    \"shortDescription\": \"Convertir le format TSV au format JSON\",\n    \"title\": \"TSV vers JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/image.json",
    "content": "{\n  \"changeColors\": {\n    \"description\": \"Monde\",\n    \"shortDescription\": \"Échanger rapidement les couleurs d'une image\",\n    \"title\": \"Changer une couleur dans une image\"\n  },\n  \"changeOpacity\": {\n    \"description\": \"Ajustez facilement la transparence de vos images. Téléchargez simplement votre image, utilisez le curseur pour définir le niveau d'opacité souhaité entre 0 (transparent) et 1 (opaque), puis téléchargez l'image modifiée.\",\n    \"shortDescription\": \"Ajuster la transparence des images\",\n    \"title\": \"Modifier l'opacité de l'image\"\n  },\n  \"compress\": {\n    \"compressedSize\": \"Taille compressée\",\n    \"compressionOptions\": \"Options de compression\",\n    \"description\": \"Réduisez la taille du fichier image tout en conservant la qualité.\",\n    \"failedToCompress\": \"Échec de la compression de l'image. Veuillez réessayer.\",\n    \"fileSizes\": \"Tailles de fichiers\",\n    \"inputTitle\": \"Image d'entrée\",\n    \"maxFileSizeDescription\": \"Taille maximale du fichier en mégaoctets\",\n    \"originalSize\": \"Taille originale\",\n    \"qualityDescription\": \"Pourcentage de qualité d'image (plus faible signifie une taille de fichier plus petite)\",\n    \"resultTitle\": \"Image compressée\",\n    \"shortDescription\": \"Compresser des images en maintenant une qualité raisonnable.\",\n    \"title\": \"Compresser une image\"\n  },\n  \"compressPng\": {\n    \"description\": \"C'est un programme pour compresser des images PNG. Dès que vous collez votre image PNG dans la zone d'entrée, le programme va la compresser et afficher le résultat dans la zone de sortie. Dans les options, vous pouvez ajuster le niveau de compression et comparer la taille des 2 fichiers.\",\n    \"shortDescription\": \"Compresser rapidement un PNG\",\n    \"title\": \"Compresser un PNG\"\n  },\n  \"convertJgpToPng\": {\n    \"description\": \"Convertissez rapidement vos images JPG en PNG. Importez simplement votre image PNG dans l'éditeur à gauche.\",\n    \"shortDescription\": \"Convertir rapidement vos images JPG en PNG\",\n    \"title\": \"Convertir un JPG en PNG\"\n  },\n  \"convertToJpg\": {\n    \"description\": \"Convertissez différents formats d'image (PNG, GIF, TIF, PSD, SVG, WEBP, HEIC, RAW) en JPG avec des paramètres de qualité et de couleur d'arrière-plan personnalisables.\",\n    \"shortDescription\": \"Convertir des images en JPG avec contrôle de la qualité\",\n    \"title\": \"Convertir des images en JPG\"\n  },\n  \"createTransparent\": {\n    \"description\": \"Monde\",\n    \"shortDescription\": \"Rendre rapidement une image transparente\",\n    \"title\": \"Créer un PNG transparent\"\n  },\n  \"crop\": {\n    \"description\": \"Recadrez les images pour supprimer les zones indésirables.\",\n    \"inputTitle\": \"Image d'entrée\",\n    \"resultTitle\": \"Image rognée\",\n    \"shortDescription\": \"Rogner des images rapidement.\",\n    \"title\": \"Rogner une image\"\n  },\n  \"editor\": {\n    \"description\": \"Éditeur d'images avancé avec des outils de recadrage, de rotation, d'annotation, d'ajustement des couleurs et d'ajout de filigranes. Retouchez vos images avec des outils professionnels directement dans votre navigateur.\",\n    \"shortDescription\": \"Modifiez des images avec des outils et des fonctionnalités avancés\",\n    \"title\": \"Éditeur d'images\"\n  },\n  \"imageToText\": {\n    \"description\": \"Extraire du texte à partir d'images (JPG, PNG) à l'aide de la reconnaissance optique de caractères (OCR).\",\n    \"shortDescription\": \"Extraire du texte à partir d'images à l'aide de l'OCR.\",\n    \"title\": \"Conversion d'image en texte (OCR)\"\n  },\n  \"qrCode\": {\n    \"description\": \"Générez des codes QR pour différents types de données : URL, texte, e-mail, téléphone, SMS, WiFi, vCard, etc.\",\n    \"shortDescription\": \"Créez des codes QR personnalisés pour différents formats de données.\",\n    \"title\": \"Générateur de code QR\"\n  },\n  \"removeBackground\": {\n    \"description\": \"Monde\",\n    \"shortDescription\": \"Supprimer automatiquement les arrière-plans des images\",\n    \"title\": \"Supprimer l'arrière-plan de l'image\"\n  },\n  \"resize\": {\n    \"description\": \"Redimensionner les images à différentes dimensions.\",\n    \"dimensionType\": \"Type de dimension\",\n    \"heightDescription\": \"Hauteur (en pixels)\",\n    \"inputTitle\": \"Image d'entrée\",\n    \"maintainAspectRatio\": \"Maintenir le rapport hauteur/largeur\",\n    \"maintainAspectRatioDescription\": \"Conserver le rapport hauteur/largeur d'origine de l'image.\",\n    \"percentage\": \"Pourcentage\",\n    \"percentageDescription\": \"Pourcentage de la taille d'origine (par exemple, 50 pour la demi-taille, 200 pour la double taille)\",\n    \"resizeByPercentage\": \"Redimensionner par pourcentage\",\n    \"resizeByPercentageDescription\": \"Redimensionner en spécifiant un pourcentage de la taille d'origine.\",\n    \"resizeByPixels\": \"Redimensionner par pixels\",\n    \"resizeByPixelsDescription\": \"Redimensionner en spécifiant les dimensions en pixels.\",\n    \"resizeMethod\": \"Méthode de redimensionnement\",\n    \"resultTitle\": \"Image redimensionnée\",\n    \"setHeight\": \"Définir la hauteur\",\n    \"setHeightDescription\": \"Spécifiez la hauteur en pixels et calculez la largeur en fonction du rapport hauteur/largeur.\",\n    \"setWidth\": \"Définir la largeur\",\n    \"setWidthDescription\": \"Spécifiez la largeur en pixels et calculez la hauteur en fonction du rapport hauteur/largeur.\",\n    \"shortDescription\": \"Redimensionnez facilement les images.\",\n    \"title\": \"Redimensionner l'image\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de redimensionner des images JPG, PNG, SVG ou GIF. Vous pouvez redimensionner en spécifiant les dimensions en pixels ou en pourcentage, avec des options permettant de conserver le rapport hauteur/largeur d'origine.\",\n      \"title\": \"Redimensionner l'image\"\n    },\n    \"widthDescription\": \"Largeur (en pixels)\"\n  },\n  \"rotate\": {\n    \"description\": \"Faire pivoter une image selon un angle spécifié.\",\n    \"shortDescription\": \"Faites pivoter une image facilement.\",\n    \"title\": \"Faire pivoter l'image\"\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/json.json",
    "content": "{\n  \"comparison\": {\n    \"description\": \"Comparez deux objets JSON pour identifier les différences de structure et de valeurs.\",\n    \"shortDescription\": \"Trouver les différences entre deux objets JSON\",\n    \"title\": \"Comparer JSON\"\n  },\n  \"escapeJson\": {\n    \"description\": \"Échappez les caractères spéciaux dans les chaînes JSON. Convertissez les données JSON au format correctement échappé pour une transmission ou un stockage sécurisé.\",\n    \"shortDescription\": \"Échapper aux caractères spéciaux dans JSON\",\n    \"title\": \"Échapper le JSON\"\n  },\n  \"jsonToXml\": {\n    \"description\": \"Convertissez des données JSON au format XML. Transformez des objets JSON structurés en documents XML bien formés.\",\n    \"shortDescription\": \"Convertir JSON au format XML\",\n    \"title\": \"JSON vers XML\"\n  },\n  \"minify\": {\n    \"description\": \"Supprimez tous les espaces inutiles du JSON.\",\n    \"inputTitle\": \"Entrée JSON\",\n    \"resultTitle\": \"JSON minifié\",\n    \"shortDescription\": \"Minifier JSON en supprimant les espaces\",\n    \"title\": \"Minifier JSON\",\n    \"toolInfo\": {\n      \"description\": \"La minification JSON consiste à supprimer tous les espaces inutiles des données JSON tout en préservant leur validité. Cela inclut la suppression des espaces, des sauts de ligne et des indentations inutiles à l'analyse correcte du JSON. La minification réduit la taille des données JSON, les rendant plus efficaces pour le stockage et la transmission, tout en conservant la même structure de données et les mêmes valeurs.\",\n      \"title\": \"Qu'est-ce que la minification JSON ?\"\n    }\n  },\n  \"prettify\": {\n    \"description\": \"Formatez JSON avec une indentation et un espacement appropriés.\",\n    \"indentation\": \"Échancrure\",\n    \"inputTitle\": \"Entrée JSON\",\n    \"resultTitle\": \"JSON embelli\",\n    \"shortDescription\": \"Formater et embellir le code JSON\",\n    \"title\": \"Embellir JSON\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de formater les données JSON avec une indentation et un espacement appropriés, les rendant plus lisibles et plus faciles à utiliser.\",\n      \"title\": \"Embellir JSON\"\n    },\n    \"useSpaces\": \"Utiliser les espaces\",\n    \"useSpacesDescription\": \"Indenter la sortie avec des espaces\",\n    \"useTabs\": \"Utiliser les tabulations\",\n    \"useTabsDescription\": \"Indenter la sortie avec des tabulations.\"\n  },\n  \"stringify\": {\n    \"description\": \"Convertissez des objets JavaScript au format de chaîne JSON. Sérialisez des structures de données en chaînes JSON pour le stockage ou la transmission.\",\n    \"shortDescription\": \"Convertir des objets en chaîne JSON\",\n    \"title\": \"Stringify JSON\"\n  },\n  \"validateJson\": {\n    \"description\": \"Vérifiez si JSON est valide et bien formé.\",\n    \"inputTitle\": \"Entrée JSON\",\n    \"invalidJson\": \"❌ {{error}}\",\n    \"resultTitle\": \"Résultat de validation\",\n    \"shortDescription\": \"Valider le code JSON pour les erreurs\",\n    \"title\": \"Valider JSON\",\n    \"toolInfo\": {\n      \"description\": \"JSON (JavaScript Object Notation) est un format d'échange de données léger. La validation JSON garantit la conformité de la structure des données à la norme JSON. Un objet JSON valide doit comporter : - des noms de propriétés entre guillemets ; - des accolades {} correctement équilibrées ; - aucune virgule après la dernière paire clé-valeur ; - une imbrication correcte des objets et des tableaux. Cet outil vérifie le JSON d'entrée et fournit des informations pour identifier et corriger les erreurs courantes.\",\n      \"title\": \"Qu'est-ce que la validation JSON ?\"\n    },\n    \"validJson\": \"✅ JSON valide\"\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/list.json",
    "content": "{\n  \"duplicate\": {\n    \"concatenate\": \"Concatener\",\n    \"concatenateDescription\": \"Concaténer les copies (si cette option n'est pas cochée, les éléments seront entrelacés)\",\n    \"copyDescription\": \"Nombre de copies (fractionnable)\",\n    \"description\": \"L'utilitaire de duplication d'éléments de liste le plus simple au monde, accessible depuis un navigateur. Saisissez votre liste et spécifiez des critères de duplication pour créer des copies d'éléments. Idéal pour l'expansion de données, les tests ou la création de modèles répétitifs.\",\n    \"duplicationOptions\": \"Options de Duplication\",\n    \"error\": \"Erreur\",\n    \"example1Description\": \"Cet exemple montre comment dupliquer une liste de mots.\",\n    \"example1Title\": \"Duplication simple\",\n    \"example2Description\": \"Cet exemple montre comment dupliquer une liste dans l’ordre inverse.\",\n    \"example2Title\": \"Duplication inversée\",\n    \"example3Description\": \"Cet exemple montre comment entrelacer des éléments au lieu de les concaténer.\",\n    \"example3Title\": \"Éléments entrelacés\",\n    \"example4Description\": \"Cet exemple montre comment dupliquer une liste avec un nombre fractionnaire de copies.\",\n    \"example4Title\": \"duplication fractionnaire\",\n    \"examples\": {\n      \"fractional\": {\n        \"description\": \"Cet exemple montre comment dupliquer une liste avec un nombre fractionnaire de copies.\",\n        \"title\": \"duplication fractionnaire\"\n      },\n      \"interweave\": {\n        \"description\": \"Cet exemple montre comment entrelacer des éléments au lieu de les concaténer.\",\n        \"title\": \"Éléments entrelacés\"\n      },\n      \"reverse\": {\n        \"description\": \"Cet exemple montre comment dupliquer une liste dans l’ordre inverse.\",\n        \"title\": \"Duplication inversée\"\n      },\n      \"simple\": {\n        \"description\": \"Cet exemple montre comment dupliquer une liste de mots.\",\n        \"title\": \"Duplication simple\"\n      }\n    },\n    \"inputTitle\": \"Liste d'entrées\",\n    \"joinSeparatorDescription\": \"Séparateur pour joindre la liste dupliquée\",\n    \"resultTitle\": \"Liste dupliquée\",\n    \"reverse\": \"Inverse\",\n    \"reverseDescription\": \"Inverser les éléments dupliqués\",\n    \"shortDescription\": \"Dupliquer les éléments de la liste avec des critères spécifiés\",\n    \"splitByRegex\": \"Diviser par expression régulière\",\n    \"splitBySymbol\": \"Diviser par symbole\",\n    \"splitOptions\": \"Options de fractionnement\",\n    \"splitSeparatorDescription\": \"Séparateur pour diviser la liste\",\n    \"title\": \"Double\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de dupliquer des éléments d'une liste. Vous pouvez spécifier le nombre de copies (y compris les valeurs fractionnaires), contrôler si les éléments sont concaténés ou entrelacés, et même inverser les éléments dupliqués. Il est utile pour créer des motifs répétés, générer des données de test ou étendre des listes au contenu prévisible.\",\n      \"title\": \"Duplication de liste\"\n    },\n    \"unknownError\": \"Une erreur inconnue s'est produite\",\n    \"validation\": {\n      \"copyMustBeNumber\": \"Le nombre de copies doit être un nombre\",\n      \"copyMustBePositive\": \"Le nombre de copies doit être positif\",\n      \"copyRequired\": \"Le nombre de copies est requis\",\n      \"joinSeparatorRequired\": \"Le séparateur de jointure est obligatoire\",\n      \"separatorRequired\": \"Le séparateur est obligatoire\"\n    }\n  },\n  \"findMostPopular\": {\n    \"description\": \"L'utilitaire de recherche par navigateur le plus simple au monde pour trouver les éléments les plus populaires d'une liste. Saisissez votre liste et obtenez instantanément les éléments qui apparaissent le plus fréquemment. Idéal pour l'analyse de données, l'identification de tendances ou la recherche d'éléments communs.\",\n    \"displayFormatDescription\": \"Comment afficher les éléments de liste les plus populaires ?\",\n    \"displayOptions\": {\n      \"count\": \"Afficher le nombre d'articles\",\n      \"percentage\": \"Afficher le pourcentage de l'article\",\n      \"total\": \"Afficher le total des articles\"\n    },\n    \"extractListItems\": \"Comment extraire des éléments de liste ?\",\n    \"ignoreItemCase\": \"Ignorer la casse de l'élément\",\n    \"ignoreItemCaseDescription\": \"Comparez tous les éléments de la liste en minuscules.\",\n    \"inputTitle\": \"Liste d'entrée\",\n    \"itemComparison\": \"Comparaison d'articles\",\n    \"outputFormat\": \"Format de sortie de l'élément principal\",\n    \"removeEmptyItems\": \"Supprimer les éléments vides\",\n    \"removeEmptyItemsDescription\": \"Ignorer les éléments vides de la comparaison.\",\n    \"resultTitle\": \"Articles les plus populaires\",\n    \"shortDescription\": \"Trouver les éléments les plus fréquemment utilisés\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Trier par ordre alphabétique\",\n      \"count\": \"Trier par nombre\"\n    },\n    \"sortingMethodDescription\": \"Sélectionnez une méthode de tri.\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Délimitez les éléments de la liste d’entrée avec une expression régulière.\",\n        \"title\": \"Utiliser une expression régulière pour le fractionnement\"\n      },\n      \"symbol\": {\n        \"description\": \"Délimitez les éléments de la liste d'entrée avec un caractère.\",\n        \"title\": \"Utiliser un symbole pour le fractionnement\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Définissez un symbole de délimitation ou une expression régulière.\",\n    \"title\": \"Trouver les plus populaires\",\n    \"trimItems\": \"Réduire les éléments de la liste principale\",\n    \"trimItemsDescription\": \"Supprimez les espaces de début et de fin avant de comparer les éléments\"\n  },\n  \"findUnique\": {\n    \"caseSensitiveItems\": \"Éléments sensibles à la casse\",\n    \"caseSensitiveItemsDescription\": \"Affichez les éléments avec une casse différente comme éléments uniques dans la liste.\",\n    \"delimiterDescription\": \"Définissez un symbole de délimitation ou une expression régulière.\",\n    \"description\": \"L'utilitaire de recherche d'éléments uniques dans une liste, le plus simple au monde, est disponible sur navigateur. Saisissez votre liste et obtenez instantanément toutes les valeurs uniques, sans doublons. Idéal pour le nettoyage des données, la déduplication ou la recherche d'éléments distincts.\",\n    \"findAbsolutelyUniqueItems\": \"Trouvez des articles absolument uniques\",\n    \"findAbsolutelyUniqueItemsDescription\": \"Afficher uniquement les éléments de la liste qui existent en un seul exemplaire.\",\n    \"inputListDelimiter\": \"Délimiteur de liste d'entrée\",\n    \"inputTitle\": \"Liste d'entrées\",\n    \"outputListDelimiter\": \"Délimiteur de liste de sortie\",\n    \"resultTitle\": \"Articles uniques\",\n    \"shortDescription\": \"Trouver des éléments uniques dans une liste\",\n    \"skipEmptyItems\": \"Ignorer les éléments vides\",\n    \"skipEmptyItemsDescription\": \"N'incluez pas les éléments de liste vides dans la sortie.\",\n    \"title\": \"Trouvez l'unique\",\n    \"trimItems\": \"Éléments de la liste de finition\",\n    \"trimItemsDescription\": \"Supprimez les espaces de début et de fin avant de comparer les éléments.\",\n    \"uniqueItemOptions\": \"Options d'articles uniques\"\n  },\n  \"group\": {\n    \"deleteEmptyItems\": \"Supprimer les éléments vides\",\n    \"deleteEmptyItemsDescription\": \"Ignorez les éléments vides et ne les incluez pas dans les groupes.\",\n    \"description\": \"L'utilitaire de regroupement d'éléments de liste le plus simple au monde, accessible depuis un navigateur. Saisissez votre liste et spécifiez des critères de regroupement pour organiser les éléments en groupes logiques. Idéal pour catégoriser des données, organiser des informations ou créer des listes structurées. Prend en charge les séparateurs personnalisés et diverses options de regroupement.\",\n    \"emptyItemsAndPadding\": \"Éléments vides et remplissage\",\n    \"groupNumberDescription\": \"Nombre d'éléments dans un groupe\",\n    \"groupSeparatorDescription\": \"Caractère séparateur de groupe\",\n    \"groupSizeAndSeparators\": \"Taille du groupe et séparateurs\",\n    \"inputItemSeparator\": \"Séparateur d'éléments d'entrée\",\n    \"inputTitle\": \"Liste d'entrée\",\n    \"itemSeparatorDescription\": \"Caractère séparateur d'éléments\",\n    \"leftWrapDescription\": \"Symbole d'enroulement gauche du groupe.\",\n    \"padNonFullGroups\": \"Groupes non complets de Pad\",\n    \"padNonFullGroupsDescription\": \"Remplissez les groupes non complets avec un élément personnalisé (saisissez ci-dessous).\",\n    \"paddingCharDescription\": \"Utilisez ce caractère ou cet objet pour compléter les groupes non complets.\",\n    \"resultTitle\": \"Articles groupés\",\n    \"rightWrapDescription\": \"Symbole d'enveloppement droit du groupe.\",\n    \"shortDescription\": \"Regrouper les éléments de la liste par propriétés communes\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Délimitez les éléments de la liste d’entrée avec une expression régulière.\",\n        \"title\": \"Utiliser une expression régulière pour le fractionnement\"\n      },\n      \"symbol\": {\n        \"description\": \"Délimitez les éléments de la liste d'entrée avec un caractère.\",\n        \"title\": \"Utiliser un symbole pour le fractionnement\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Définissez un symbole de délimitation ou une expression régulière.\",\n    \"title\": \"Groupe\"\n  },\n  \"reverse\": {\n    \"description\": \"Cette application très simple, basée sur un navigateur, imprime tous les éléments d'une liste à l'envers. Les éléments saisis peuvent être séparés par n'importe quel symbole et vous pouvez également modifier le séparateur des éléments inversés.\",\n    \"inputTitle\": \"Liste d'entrée\",\n    \"itemSeparator\": \"Séparateur d'éléments\",\n    \"itemSeparatorDescription\": \"Définissez un symbole de délimitation ou une expression régulière.\",\n    \"outputListOptions\": \"Options de la liste de sortie\",\n    \"outputSeparatorDescription\": \"Séparateur d'éléments de liste de sortie.\",\n    \"resultTitle\": \"Liste inversée\",\n    \"shortDescription\": \"Inverser rapidement une liste\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Délimitez les éléments de la liste d’entrée avec une expression régulière.\",\n        \"title\": \"Utiliser une expression régulière pour le fractionnement\"\n      },\n      \"symbol\": {\n        \"description\": \"Délimitez les éléments de la liste d'entrée avec un caractère.\",\n        \"title\": \"Utiliser un symbole pour le fractionnement\"\n      }\n    },\n    \"splitterMode\": \"Mode séparateur\",\n    \"title\": \"Inverse\",\n    \"toolInfo\": {\n      \"description\": \"Cet utilitaire permet d'inverser l'ordre des éléments d'une liste. Il divise d'abord la liste d'entrée en éléments individuels, puis les parcourt du dernier au premier, en affichant chaque élément dans la sortie au cours de l'itération. La liste d'entrée peut contenir tout ce qui peut être représenté sous forme de données textuelles, notamment des chiffres, des nombres, des chaînes de caractères, des mots, des phrases, etc. Le séparateur d'éléments d'entrée peut également être une expression régulière. Par exemple, l'expression régulière /[;,]/ permet d'utiliser des éléments séparés par des virgules ou des points-virgules. Les délimiteurs des éléments d'entrée et de sortie sont personnalisables dans les options. Par défaut, les listes d'entrée et de sortie sont séparées par des virgules. Incroyable !\",\n      \"title\": \"Qu'est-ce qu'un inverseur de liste ?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"L'utilitaire de rotation des éléments de liste le plus simple au monde, basé sur un navigateur. Saisissez votre liste et spécifiez le degré de rotation pour décaler les éléments d'un nombre de positions spécifié. Idéal pour la manipulation de données, les décalages circulaires ou la réorganisation des listes.\",\n    \"shortDescription\": \"Faire pivoter les éléments de la liste selon des positions spécifiées\",\n    \"title\": \"Tourner\"\n  },\n  \"shuffle\": {\n    \"delimiterDescription\": \"Définissez un symbole de délimitation ou une expression régulière.\",\n    \"description\": \"L'utilitaire de navigation le plus simple au monde pour mélanger les éléments d'une liste. Saisissez votre liste et obtenez instantanément une version aléatoire avec les éléments dans un ordre aléatoire. Idéal pour varier, tester le caractère aléatoire ou mélanger des données ordonnées.\",\n    \"inputListSeparator\": \"Séparateur de liste d'entrée\",\n    \"inputTitle\": \"Liste d'entrée\",\n    \"joinSeparatorDescription\": \"Utilisez ce séparateur dans la liste aléatoire.\",\n    \"outputLengthDescription\": \"Afficher autant d'éléments aléatoires\",\n    \"resultTitle\": \"Liste mélangée\",\n    \"shortDescription\": \"Randomiser l'ordre des éléments de la liste\",\n    \"shuffledListLength\": \"Longueur de la liste mélangée\",\n    \"shuffledListSeparator\": \"Séparateur de liste mélangée\",\n    \"title\": \"Mélanger\"\n  },\n  \"sort\": {\n    \"caseSensitive\": \"Tri sensible à la casse\",\n    \"caseSensitiveDescription\": \"Trier les majuscules et les minuscules séparément. Les majuscules précèdent les minuscules dans une liste ascendante. (Fonctionne uniquement en mode de tri alphabétique.)\",\n    \"description\": \"L'utilitaire de tri de listes le plus simple au monde, basé sur un navigateur. Saisissez votre liste et spécifiez des critères de tri pour organiser les éléments par ordre croissant ou décroissant. Idéal pour l'organisation de données, le traitement de texte ou la création de listes ordonnées.\",\n    \"inputItemSeparator\": \"Séparateur d'éléments d'entrée\",\n    \"inputTitle\": \"Liste d'entrée\",\n    \"joinSeparatorDescription\": \"Utilisez ce symbole comme jointure entre les éléments d'une liste triée.\",\n    \"orderDescription\": \"Sélectionnez un ordre de tri.\",\n    \"orderOptions\": {\n      \"decreasing\": \"Ordre décroissant\",\n      \"increasing\": \"Ordre croissant\"\n    },\n    \"removeDuplicates\": \"Supprimer les doublons\",\n    \"removeDuplicatesDescription\": \"Supprimer les éléments de liste en double.\",\n    \"resultTitle\": \"Liste triée\",\n    \"shortDescription\": \"Trier les éléments de la liste dans l'ordre spécifié\",\n    \"sortMethod\": \"Méthode de tri\",\n    \"sortMethodDescription\": \"Sélectionnez une méthode de tri.\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Trier par ordre alphabétique\",\n      \"length\": \"Trier par longueur\",\n      \"numeric\": \"Trier numériquement\"\n    },\n    \"sortedItemProperties\": \"Propriétés des éléments triés\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Délimitez les éléments de la liste d’entrée avec une expression régulière.\",\n        \"title\": \"Utiliser une expression régulière pour le fractionnement\"\n      },\n      \"symbol\": {\n        \"description\": \"Délimitez les éléments de la liste d'entrée avec un caractère.\",\n        \"title\": \"Utiliser un symbole pour le fractionnement\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Définissez un symbole de délimitation ou une expression régulière.\",\n    \"title\": \"Trier\"\n  },\n  \"truncate\": {\n    \"description\": \"L'utilitaire de tronquage de listes le plus simple au monde, accessible depuis un navigateur. Saisissez votre liste et spécifiez le nombre maximal d'éléments à conserver. Idéal pour le traitement de données, la gestion de listes ou la limitation de la longueur du contenu.\",\n    \"shortDescription\": \"Tronquer la liste au nombre spécifié d'éléments\",\n    \"title\": \"Tronquer\"\n  },\n  \"unwrap\": {\n    \"description\": \"L'utilitaire de dépliage de listes le plus simple au monde, basé sur un navigateur. Saisissez votre liste dépliée et spécifiez des critères de dépliage pour aplatir les éléments organisés. Idéal pour le traitement de données, la manipulation de texte ou l'extraction de contenu de listes structurées.\",\n    \"shortDescription\": \"Décompresser les éléments de la liste à partir du format structuré\",\n    \"title\": \"Déballer\"\n  },\n  \"wrap\": {\n    \"description\": \"Ajoutez du texte avant et après chaque élément de la liste.\",\n    \"inputTitle\": \"Liste d'entrées\",\n    \"joinSeparatorDescription\": \"Séparateur pour joindre la liste enveloppée\",\n    \"leftTextDescription\": \"Texte à ajouter avant chaque élément\",\n    \"removeEmptyItems\": \"Supprimer les éléments vides\",\n    \"resultTitle\": \"Liste enveloppée\",\n    \"rightTextDescription\": \"Texte à ajouter après chaque élément\",\n    \"shortDescription\": \"Envelopper les éléments de la liste avec des critères spécifiés\",\n    \"splitByRegex\": \"Diviser par expression régulière\",\n    \"splitBySymbol\": \"Diviser par symbole\",\n    \"splitOptions\": \"Options de fractionnement\",\n    \"splitSeparatorDescription\": \"Séparateur pour diviser la liste\",\n    \"title\": \"Envelopper\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet d'ajouter du texte avant et après chaque élément d'une liste. Vous pouvez spécifier un texte différent pour les côtés gauche et droit, et contrôler le traitement de la liste. Il est utile pour ajouter des guillemets, des crochets ou d'autres éléments de mise en forme aux éléments d'une liste, préparer des données pour différents formats ou créer du texte structuré.\",\n      \"title\": \"Enveloppement de liste\"\n    },\n    \"wrapOptions\": \"Options d'enveloppement\"\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/number.json",
    "content": "{\n  \"arithmeticSequence\": {\n    \"commonDifferenceDescription\": \"Différence courante entre les termes (d)\",\n    \"description\": \"Générez des séquences arithmétiques avec des paramètres personnalisables.\",\n    \"firstTermDescription\": \"Premier terme de la suite (a₁)\",\n    \"numberOfTermsDescription\": \"Nombre de termes à générer (n)\",\n    \"outputFormat\": \"Format de sortie\",\n    \"resultTitle\": \"Séquence générée\",\n    \"separatorDescription\": \"Séparateur entre les termes\",\n    \"sequenceParameters\": \"Paramètres de séquence\",\n    \"shortDescription\": \"Générer des séquences arithmétiques\",\n    \"title\": \"Séquence arithmétique\",\n    \"toolInfo\": {\n      \"description\": \"Une suite arithmétique est une suite de nombres dont la différence entre chaque terme consécutif est constante. Cette différence constante est appelée différence commune. Étant donné le premier terme (a₁) et la différence commune (d), chaque terme peut être trouvé en additionnant la différence commune au terme précédent.\",\n      \"title\": \"Qu'est-ce qu'une suite arithmétique ?\"\n    }\n  },\n  \"generate\": {\n    \"arithmeticSequenceOption\": \"Option de séquence arithmétique\",\n    \"description\": \"Générer une séquence de nombres avec des paramètres personnalisables.\",\n    \"numberOfElementsDescription\": \"Nombre d'éléments dans la séquence.\",\n    \"resultTitle\": \"Numéros générés\",\n    \"separator\": \"Séparateur\",\n    \"separatorDescription\": \"Séparez les éléments de la séquence arithmétique par ce caractère.\",\n    \"shortDescription\": \"Générer des nombres aléatoires dans des plages spécifiées\",\n    \"startSequenceDescription\": \"Démarrer la séquence à partir de ce numéro.\",\n    \"stepDescription\": \"Augmentez chaque élément de ce montant\",\n    \"title\": \"Générer\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de générer une séquence de nombres avec des paramètres personnalisables. Vous pouvez spécifier la valeur de départ, le pas et le nombre d'éléments.\",\n      \"title\": \"Générer des nombres\"\n    }\n  },\n  \"ohmsLaw\": {\n    \"description\": \"Calcule la tension, le courant et la résistance\",\n    \"longDescription\": \"Cette calculatrice applique la loi d'Ohm (V = I × R) pour déterminer n'importe lequel des trois paramètres électriques lorsque les deux autres sont connus. La loi d'Ohm est un principe fondamental du génie électrique qui décrit la relation entre la tension (V), le courant (I) et la résistance (R). Cet outil est essentiel pour les électroniciens amateurs, les ingénieurs électriciens et les étudiants travaillant sur des circuits afin de résoudre rapidement les valeurs inconnues de leurs conceptions électriques.\",\n    \"shortDescription\": \"Calculer la tension, le courant ou la résistance dans les circuits électriques en utilisant la loi d'Ohm\",\n    \"title\": \"loi d'Ohm\"\n  },\n  \"randomNumberGenerator\": {\n    \"description\": \"Générez des nombres aléatoires dans une plage spécifiée avec des options personnalisables.\",\n    \"error\": {\n      \"generationFailed\": \"Échec de la génération de nombres aléatoires. Veuillez vérifier vos paramètres d'entrée.\"\n    },\n    \"info\": {\n      \"description\": \"Un générateur de nombres aléatoires crée des nombres imprévisibles dans une plage spécifiée. Cet outil utilise une génération de nombres aléatoires sécurisée par cryptographie pour garantir des résultats véritablement aléatoires. Il est utile pour les simulations, les jeux, l'échantillonnage statistique et les scénarios de test.\",\n      \"title\": \"Qu'est-ce qu'un générateur de nombres aléatoires ?\"\n    },\n    \"longDescription\": \"Générez des nombres aléatoires dans une plage spécifiée avec des options pour les entiers ou les décimales, autorisez ou empêchez les doublons et triez les résultats. Idéal pour les simulations, les tests, les jeux et l'analyse statistique.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDecimals\": {\n          \"description\": \"Générer des nombres décimaux au lieu d'entiers\",\n          \"title\": \"Autoriser les nombres décimaux\"\n        },\n        \"allowDuplicates\": {\n          \"description\": \"Autoriser le même numéro à apparaître plusieurs fois\",\n          \"title\": \"Autoriser les doublons\"\n        },\n        \"countDescription\": \"Nombre de nombres aléatoires à générer (1 à 10 000)\",\n        \"sortResults\": {\n          \"description\": \"Trier les nombres générés par ordre croissant\",\n          \"title\": \"Trier les résultats\"\n        },\n        \"title\": \"Options de génération\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Caractère(s) pour séparer les nombres générés\",\n        \"title\": \"Paramètres de sortie\"\n      },\n      \"range\": {\n        \"maxDescription\": \"Valeur maximale (incluse)\",\n        \"minDescription\": \"Valeur minimale (incluse)\",\n        \"title\": \"Paramètres de plage\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Compter\",\n      \"hasDuplicates\": \"Contient des doublons\",\n      \"isSorted\": \"Trié\",\n      \"range\": \"Gamme\",\n      \"title\": \"Nombres aléatoires générés\"\n    },\n    \"shortDescription\": \"Générer des nombres aléatoires dans des plages personnalisées\",\n    \"title\": \"Générateur de nombres aléatoires\"\n  },\n  \"randomPortGenerator\": {\n    \"description\": \"Générez des ports réseau aléatoires dans des plages spécifiées avec des options personnalisables.\",\n    \"error\": {\n      \"generationFailed\": \"Échec de la génération de ports aléatoires. Veuillez vérifier vos paramètres d'entrée.\"\n    },\n    \"info\": {\n      \"description\": \"Un générateur de ports aléatoires crée des numéros de ports réseau imprévisibles dans des plages spécifiées. Cet outil respecte les normes IANA relatives aux numéros de ports et inclut l'identification des services courants. Il est utile pour le développement, les tests, la configuration réseau et la prévention des conflits de ports.\",\n      \"title\": \"Qu'est-ce qu'un générateur de ports aléatoires ?\"\n    },\n    \"longDescription\": \"Générez des ports réseau aléatoires dans des plages spécifiées (connues, enregistrées, dynamiques ou personnalisées). Idéal pour le développement, les tests et la configuration réseau. Inclut l'identification des services de port pour les ports courants.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDuplicates\": {\n          \"description\": \"Autoriser le même port à apparaître plusieurs fois\",\n          \"title\": \"Autoriser les doublons\"\n        },\n        \"countDescription\": \"Nombre de ports aléatoires à générer (1 à 1 000)\",\n        \"sortResults\": {\n          \"description\": \"Trier les ports générés par ordre croissant\",\n          \"title\": \"Trier les résultats\"\n        },\n        \"title\": \"Options de génération\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Caractère(s) pour séparer les ports générés\",\n        \"title\": \"Paramètres de sortie\"\n      },\n      \"range\": {\n        \"custom\": \"Gamme personnalisée\",\n        \"dynamic\": \"Ports dynamiques (49152-65535)\",\n        \"maxPortDescription\": \"Numéro de port maximal (1-65535)\",\n        \"minPortDescription\": \"Numéro de port minimum (1-65535)\",\n        \"registered\": \"Ports enregistrés (1024-49151)\",\n        \"title\": \"Paramètres de plage de ports\",\n        \"wellKnown\": \"Ports connus (1-1023)\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Compter\",\n      \"hasDuplicates\": \"Contient des doublons\",\n      \"isSorted\": \"Trié\",\n      \"portDetails\": \"Détails du port\",\n      \"range\": \"Port Range\",\n      \"title\": \"Ports aléatoires générés\"\n    },\n    \"shortDescription\": \"Générer des ports réseau aléatoires\",\n    \"title\": \"Générateur de ports aléatoires\"\n  },\n  \"slackline\": {\n    \"description\": \"Calcule la tension dans une slackline\",\n    \"longDescription\": \"Cette calculatrice suppose une charge au centre de la corde\",\n    \"shortDescription\": \"Calculez la tension approximative d'une slackline ou d'une corde à linge. Ne vous fiez pas à cela pour votre sécurité.\",\n    \"title\": \"Tension de la slackline\"\n  },\n  \"sphereArea\": {\n    \"description\": \"Aire d'une sphère\",\n    \"longDescription\": \"Cette calculatrice détermine l'aire de surface d'une sphère selon la formule A = 4πr². Vous pouvez saisir le rayon pour trouver l'aire de surface ou saisir l'aire de surface pour calculer le rayon requis. Cet outil est utile aux étudiants en géométrie, aux ingénieurs travaillant avec des objets sphériques et à toute personne devant effectuer des calculs impliquant des surfaces sphériques.\",\n    \"shortDescription\": \"Calculer l'aire de surface d'une sphère en fonction de son rayon\",\n    \"title\": \"Aire d'une sphère\"\n  },\n  \"sphereVolume\": {\n    \"description\": \"Volume d'une sphère\",\n    \"longDescription\": \"Cette calculatrice calcule le volume d'une sphère selon la formule V = (4/3)πr³. Vous pouvez saisir le rayon ou le diamètre pour trouver le volume, ou saisir le volume pour déterminer le rayon requis. Cet outil est précieux pour les étudiants, les ingénieurs et les professionnels travaillant avec des objets sphériques dans des domaines tels que la physique, l'ingénierie et la fabrication.\",\n    \"shortDescription\": \"Calculer le volume d'une sphère en utilisant le rayon ou le diamètre\",\n    \"title\": \"Volume d'une sphère\"\n  },\n  \"sum\": {\n    \"description\": \"Calculez la somme d'une liste de nombres. Saisissez les nombres séparés par des virgules ou des sauts de ligne pour obtenir leur somme totale.\",\n    \"example1Description\": \"Dans cet exemple, nous calculons la somme de dix entiers positifs. Ces entiers sont répertoriés dans une colonne et leur somme totale est égale à 19 494.\",\n    \"example1Title\": \"Somme de dix nombres positifs\",\n    \"example2Description\": \"Cet exemple inverse une colonne de vingt noms de trois syllabes et affiche tous les mots de bas en haut. Pour séparer les éléments de la liste, le caractère \\\\n est utilisé comme séparateur d'entrée, ce qui signifie que chaque élément est sur sa propre ligne.\",\n    \"example2Title\": \"Compter les arbres dans le parc\",\n    \"example3Description\": \"Dans cet exemple, nous additionnons quatre-vingt-dix valeurs différentes : nombres positifs, nombres négatifs, entiers et fractions décimales. Nous utilisons une virgule comme séparateur d'entrée et, après addition, nous obtenons 0 en sortie.\",\n    \"example3Title\": \"Somme des nombres entiers et décimaux\",\n    \"example4Description\": \"Dans cet exemple, nous calculons la somme des dix chiffres et activons l'option « Imprimer la somme cumulée ». Nous obtenons les valeurs intermédiaires de la somme lors de l'addition. Nous obtenons ainsi la séquence suivante en sortie : 0, 1 (0 + 1), 3 (0 + 1 + 2), 6 (0 + 1 + 2 + 3), 10 (0 + 1 + 2 + 3 + 4), etc.\",\n    \"example4Title\": \"Somme courante de nombres\",\n    \"extractionTypes\": {\n      \"delimiter\": {\n        \"description\": \"Personnalisez le séparateur de nombres ici. (Par défaut, il s'agit d'un saut de ligne.)\",\n        \"title\": \"Délimiteur de nombre\"\n      },\n      \"smart\": {\n        \"description\": \"Détection automatique des nombres dans l'entrée.\",\n        \"title\": \"Somme intelligente\"\n      }\n    },\n    \"inputTitle\": \"Entrée\",\n    \"numberExtraction\": \"Extraction de nombres\",\n    \"printRunningSum\": \"Imprimer le total cumulé\",\n    \"printRunningSumDescription\": \"Affichez la somme telle qu'elle est calculée étape par étape.\",\n    \"resultTitle\": \"Total\",\n    \"runningSum\": \"Somme courante\",\n    \"shortDescription\": \"Calculer la somme des nombres\",\n    \"title\": \"Somme\",\n    \"toolInfo\": {\n      \"description\": \"Il s'agit d'un utilitaire en ligne, accessible depuis un navigateur, permettant de calculer la somme de plusieurs nombres. Vous pouvez saisir les nombres séparés par une virgule, un espace ou tout autre caractère, y compris le saut de ligne. Vous pouvez également simplement coller un fragment de données textuelles contenant les valeurs numériques à additionner : l'utilitaire les extraira et calculera leur somme.\",\n      \"title\": \"Qu'est-ce qu'une calculatrice de somme numérique ?\"\n    }\n  },\n  \"voltageDropInWire\": {\n    \"description\": \"Calcule la tension aller-retour et la perte de puissance dans un câble à 2 conducteurs\",\n    \"longDescription\": \"Ce calculateur permet de déterminer la chute de tension et la perte de puissance dans un câble électrique à deux conducteurs. Il prend en compte la longueur du câble, la section du fil, la résistivité du matériau et le flux de courant. Il calcule la chute de tension aller-retour, la résistance totale du câble et la puissance dissipée sous forme de chaleur. Cet outil est particulièrement utile aux ingénieurs électriciens, aux électriciens et aux bricoleurs pour la conception de systèmes électriques afin de garantir que les niveaux de tension restent dans des limites acceptables sous charge.\",\n    \"shortDescription\": \"Calculer la chute de tension et la perte de puissance dans les câbles électriques en fonction de la longueur, du matériau et du courant\",\n    \"title\": \"Chute de tension aller-retour dans le câble\"\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/pdf.json",
    "content": "{\n  \"compressPdf\": {\n    \"compressedFileSize\": \"Taille du fichier compressé\",\n    \"compressingPdf\": \"Compression des PDF...\",\n    \"compressionLevel\": \"Niveau de compression\",\n    \"compressionSettings\": \"Paramètres de compression\",\n    \"description\": \"Réduire la taille des fichiers PDF tout en maintenant la qualité grâce à Ghostscript\",\n    \"errorCompressingPdf\": \"Échec de la compression du PDF : {{error}}\",\n    \"errorReadingPdf\": \"Échec de la lecture du fichier PDF. Veuillez vous assurer qu'il s'agit d'un PDF valide.\",\n    \"fileSize\": \"Taille du fichier original\",\n    \"highCompression\": \"Compression élevée\",\n    \"highCompressionDescription\": \"Réduction maximale de la taille du fichier avec une certaine perte de qualité\",\n    \"inputTitle\": \"Entrée PDF\",\n    \"longDescription\": \"Compressez vos fichiers PDF en toute sécurité dans votre navigateur grâce à Ghostscript. Vos fichiers restent sur votre appareil, garantissant une confidentialité totale tout en réduisant leur taille pour le partage par e-mail, le téléchargement sur des sites web et l'économie d'espace de stockage. Optimisé par la technologie WebAssembly.\",\n    \"lowCompression\": \"Faible compression\",\n    \"lowCompressionDescription\": \"Réduisez légèrement la taille du fichier avec une perte de qualité minimale\",\n    \"mediumCompression\": \"Compression moyenne\",\n    \"mediumCompressionDescription\": \"Équilibre entre la taille et la qualité des fichiers\",\n    \"pages\": \"Nombre de pages\",\n    \"resultTitle\": \"PDF compressé\",\n    \"shortDescription\": \"Compresser des fichiers PDF en toute sécurité dans votre navigateur\",\n    \"title\": \"Compresser le PDF\"\n  },\n  \"editor\": {\n    \"description\": \"Éditeur PDF avancé avec fonctions d'annotation, de remplissage de formulaires, de surlignage et d'export. Modifiez vos PDF directement dans le navigateur avec des outils de qualité professionnelle, notamment l'insertion de texte, le dessin, le surlignage, la signature et le remplissage de formulaires.\",\n    \"shortDescription\": \"Modifier les PDF avec des outils avancés d'annotation, de signature et d'édition\",\n    \"title\": \"Editeur de PDF\"\n  },\n  \"merge\": {\n    \"inputTitle\": \"Entrée PDF\",\n    \"loadingText\": \"Extraire les pages\",\n    \"resultTitle\": \"Sortie du PDF fusionné\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de fusionner plusieurs fichiers PDF en un seul document. Pour utiliser cet outil, il vous suffit de télécharger les fichiers PDF que vous souhaitez fusionner. L'outil combinera ensuite toutes les pages des fichiers d'entrée en un seul document PDF.\",\n      \"title\": \"Comment utiliser l’outil de fusion de PDF ?\"\n    }\n  },\n  \"mergePdf\": {\n    \"description\": \"Comment utiliser l'outil Fusionner PDF ?\",\n    \"inputTitle\": \"PDF d'entrée\",\n    \"mergingPdfs\": \"Fusion de PDF\",\n    \"pdfOptions\": \"Options PDF\",\n    \"resultTitle\": \"PDF fusionné\",\n    \"shortDescription\": \"Fusionner plusieurs fichiers PDF en un seul document\",\n    \"sortByFileName\": \"Trier par nom de fichier\",\n    \"sortByFileNameDescription\": \"Trier les PDF par ordre alphabétique par nom de fichier\",\n    \"sortByUploadOrder\": \"Trier par ordre de téléchargement\",\n    \"sortByUploadOrderDescription\": \"Conserver les fichiers PDF dans l'ordre dans lequel ils ont été téléchargés\",\n    \"title\": \"Fusionner des PDF\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de combiner plusieurs fichiers PDF en un seul document. Vous pouvez choisir comment trier les PDF et l'outil les fusionnera dans l'ordre spécifié.\",\n      \"title\": \"Fusionner des fichiers PDF\"\n    }\n  },\n  \"pdfToEpub\": {\n    \"description\": \"Transformez les documents PDF en fichiers EPUB pour une meilleure compatibilité avec les liseuses.\",\n    \"shortDescription\": \"Convertir des fichiers PDF au format EPUB\",\n    \"title\": \"PDF vers EPUB\"\n  },\n  \"pdfToPng\": {\n    \"description\": \"Transformez les documents PDF en panneaux PNG.\",\n    \"longDescription\": \"Téléchargez un PDF et convertissez chaque page en image PNG de haute qualité directement dans votre navigateur. Cet outil est idéal pour extraire du contenu visuel ou partager des pages individuelles. Aucune donnée n'est téléchargée : tout fonctionne en local.\",\n    \"shortDescription\": \"Convertir des PDF en images PNG\",\n    \"title\": \"PDF en PNG\"\n  },\n  \"convertToPdf\": {\n    \"title\": \"Images vers PDF\",\n    \"description\": \"Convertir divers formats d'image (PNG, GIF, JPG, TIF, PSD, SVG, WEBP, HEIC, RAW) en PDF, avec des options pour redimensionner l'image et choisir l'orientation de la page.\",\n    \"shortDescription\": \"Convertir des images en PDF avec contrôle de taille et d'orientation\"\n  },\n  \"protectPdf\": {\n    \"description\": \"Ajoutez une protection par mot de passe à vos fichiers PDF en toute sécurité dans votre navigateur\",\n    \"shortDescription\": \"Protégez les fichiers PDF en toute sécurité avec un mot de passe\",\n    \"title\": \"Protéger le PDF\"\n  },\n  \"rotatePdf\": {\n    \"allPagesWillBeRotated\": \"Les {{count}} pages seront tournées\",\n    \"angleOptions\": {\n      \"clockwise90\": \"90° dans le sens des aiguilles d'une montre\",\n      \"counterClockwise270\": \"270° (90° dans le sens inverse des aiguilles d'une montre)\",\n      \"upsideDown180\": \"180° (à l'envers)\"\n    },\n    \"applyToAllPages\": \"Appliquer à toutes les pages\",\n    \"description\": \"Faire pivoter les pages d'un document PDF.\",\n    \"inputTitle\": \"Entrée PDF\",\n    \"longDescription\": \"Modifiez l'orientation des pages PDF en les faisant pivoter de 90, 180 ou 270 degrés. Utile pour corriger des documents mal numérisés ou préparer des PDF pour l'impression.\",\n    \"pageRangesDescription\": \"Saisissez les numéros de page ou les plages séparés par des virgules (par exemple, 1, 3, 5-7)\",\n    \"pageRangesPlaceholder\": \"par exemple, 1,5-8\",\n    \"pagesWillBeRotated\": \"{{count}} page{{count !== 1 ? 's' : ''}} sera tourné\",\n    \"pdfPageCount\": \"PDF a {{count}} page{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"PDF pivoté\",\n    \"rotatingPages\": \"Rotation des pages\",\n    \"rotationAngle\": \"Angle de rotation\",\n    \"rotationSettings\": \"Paramètres de rotation\",\n    \"shortDescription\": \"Faire pivoter les pages d'un document PDF\",\n    \"title\": \"Faire pivoter le PDF\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de faire pivoter les pages d'un document PDF. Vous pouvez faire pivoter toutes les pages ou spécifier des pages individuelles. Choisissez un angle de rotation : 90° dans le sens horaire, 180° (à l'envers) ou 270° (90° dans le sens antihoraire). Pour faire pivoter des pages spécifiques, décochez « Appliquer à toutes les pages » et saisissez les numéros de page ou les plages de numéros séparés par des virgules (par exemple, 1, 3, 5-7).\",\n      \"title\": \"Comment utiliser l'outil de rotation PDF\"\n    }\n  },\n  \"splitPdf\": {\n    \"description\": \"Extraire des pages spécifiques d'un document PDF.\",\n    \"extractingPages\": \"Extraire les pages\",\n    \"inputTitle\": \"Entrée PDF\",\n    \"pageExtractionPreview\": \"{{count}} page{{count !== 1 ? 's' : ''}} sera(ont) extrait(s)\",\n    \"pageRangesDescription\": \"Saisissez les numéros de page ou les plages séparés par des virgules (par exemple, 1, 3, 5-7)\",\n    \"pageRangesPlaceholder\": \"par exemple, 1,5-8\",\n    \"pageSelection\": \"Sélection de page\",\n    \"pdfPageCount\": \"PDF a {{count}} page{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"PDF extrait\",\n    \"shortDescription\": \"Extraire des pages spécifiques d'un fichier PDF\",\n    \"title\": \"Diviser le PDF\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet d'extraire des pages spécifiques d'un document PDF. Vous pouvez spécifier des pages individuelles ou des plages de pages à extraire.\",\n      \"title\": \"Diviser le PDF\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/string.json",
    "content": "{\n  \"base64\": {\n    \"decode\": \"Décodage Base64\",\n    \"description\": \"Encodez ou décodez du texte à l'aide de l'encodage Base64.\",\n    \"encode\": \"Encodage Base64\",\n    \"inputTitle\": \"Données d'entrée\",\n    \"optionsTitle\": \"Options Base64\",\n    \"resultTitle\": \"Résultat\",\n    \"shortDescription\": \"Encoder ou décoder des données à l'aide de Base64.\",\n    \"title\": \"Encodeur/décodeur Base64\",\n    \"toolInfo\": {\n      \"description\": \"Base64 est un schéma de codage qui représente les données au format chaîne ASCII en les convertissant en une représentation radix-64. Bien qu'il puisse être utilisé pour encoder des chaînes, il est couramment utilisé pour encoder des données binaires destinées à être transmises sur des supports conçus pour traiter des données textuelles.\",\n      \"title\": \"Qu'est-ce que Base64 ?\"\n    }\n  },\n  \"censor\": {\n    \"description\": \"Utilitaire de censure de mots dans un texte. Chargez votre texte dans le formulaire de saisie à gauche, spécifiez tous les mots interdits dans les options et vous obtiendrez instantanément le texte censuré dans la zone de sortie. », longDescription: « Avec cet outil en ligne, vous pouvez censurer certains mots dans n'importe quel texte. Vous pouvez spécifier une liste de mots indésirables (tels que des jurons ou des mots secrets) et le programme les remplacera par d'autres mots pour créer un texte lisible. Les mots peuvent être spécifiés dans un champ de texte multiligne dans les options, en saisissant un mot par ligne. », keywords: ['text', 'censor', 'words', 'characters'], component: lazy(() => import('./index')), i18n: { name: 'string:censor.title', description: 'string:censor.description\",\n    \"shortDescription\": \"Masquez rapidement les gros mots ou remplacez-les par des mots alternatifs.\",\n    \"title\": \"Censure de texte\"\n  },\n  \"createPalindrome\": {\n    \"description\": \"L'utilitaire de navigateur le plus simple au monde pour créer des palindromes à partir de n'importe quel texte. Saisissez du texte et transformez-le instantanément en un palindrome qui se lit de la même manière à l'endroit comme à l'envers. Idéal pour les jeux de mots, la création de motifs de texte symétriques ou l'exploration de curiosités linguistiques.\",\n    \"shortDescription\": \"Créez un texte qui se lit de la même manière à l'avant et à l'arrière\",\n    \"title\": \"Créer un palindrome\"\n  },\n  \"extractSubstring\": {\n    \"description\": \"L'utilitaire le plus simple au monde, basé sur un navigateur, pour extraire des sous-chaînes de texte. Saisissez votre texte et spécifiez les positions de début et de fin pour extraire la portion souhaitée. Idéal pour le traitement de données, l'analyse de texte ou l'extraction de contenu spécifique à partir de blocs de texte volumineux.\",\n    \"shortDescription\": \"Extraire une partie de texte entre des positions spécifiées\",\n    \"title\": \"Extraire la sous-chaîne\"\n  },\n  \"hiddenCharacterDetector\": {\n    \"analysisOptions\": \"Options d'analyse\",\n    \"category\": \"Catégorie\",\n    \"description\": \"Détectez les caractères Unicode cachés, en particulier les caractères RTL Override qui pourraient être utilisés dans des attaques.\",\n    \"foundChars\": \"Trouvé {{count}} personnage(s) caché(s) :\",\n    \"inputPlaceholder\": \"Saisissez du texte pour vérifier les caractères cachés...\",\n    \"inputTitle\": \"Texte à analyser\",\n    \"invisibleChar\": \"Personnage invisible\",\n    \"invisibleFound\": \"Personnages invisibles trouvés\",\n    \"longDescription\": \"Cet outil vous aide à détecter les caractères Unicode cachés dans un texte, notamment les caractères de substitution de droite à gauche (RTL) qui peuvent être utilisés dans des attaques. Il peut identifier les caractères invisibles, les caractères de largeur nulle et autres séquences Unicode potentiellement malveillantes qui peuvent se cacher dans un texte apparemment innocent.\",\n    \"noHiddenChars\": \"Aucun caractère caché détecté dans le texte.\",\n    \"optionsDescription\": \"Configurez les types de caractères cachés à détecter et comment afficher les résultats.\",\n    \"position\": \"Position\",\n    \"rtlAlert\": \"⚠️ Caractères de substitution RTL détectés ! Ce texte peut contenir des caractères cachés malveillants.\",\n    \"rtlFound\": \"Remplacement RTL trouvé\",\n    \"rtlOverride\": \"Caractère de remplacement RTL\",\n    \"rtlWarning\": \"AVERTISSEMENT : Caractères de substitution RTL détectés ! Ceci pourrait être utilisé dans des attaques.\",\n    \"shortDescription\": \"Rechercher les caractères Unicode cachés dans le texte\",\n    \"summary\": \"Résumé de l'analyse\",\n    \"title\": \"Détecteur de caractères cachés\",\n    \"totalChars\": \"Nombre total de caractères cachés : {{count}}\",\n    \"unicode\": \"Unicode\",\n    \"zeroWidthChar\": \"Caractère de largeur nulle\",\n    \"zeroWidthFound\": \"Caractères de largeur nulle trouvés\"\n  },\n  \"join\": {\n    \"blankLinesAndTrailingSpaces\": \"Lignes vides et espaces de fin\",\n    \"deleteBlankDescription\": \"Supprimez les lignes qui n’ont pas de symboles de texte.\",\n    \"deleteBlankTitle\": \"Supprimer les lignes vides\",\n    \"deleteTrailingDescription\": \"Supprimez les espaces et les tabulations à la fin des lignes.\",\n    \"deleteTrailingTitle\": \"Supprimer les espaces de fin\",\n    \"description\": \"Joignez des morceaux de texte avec des séparateurs personnalisables.\",\n    \"inputTitle\": \"Textes\",\n    \"joinCharacterDescription\": \"Symbole reliant les fragments de texte. (Espace par défaut.)\",\n    \"joinCharacterPlaceholder\": \"Rejoindre le personnage\",\n    \"resultTitle\": \"Texte joint\",\n    \"shortDescription\": \"Joindre des éléments de texte avec un séparateur spécifié\",\n    \"textMergedOptions\": \"Options de fusion de texte\",\n    \"title\": \"Joindre le texte\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de fusionner des parties de texte. Il prend une liste de valeurs de texte, séparées par des sauts de ligne, et les fusionne. Vous pouvez définir le caractère qui sera placé entre les parties du texte combiné. Vous pouvez également ignorer les lignes vides et supprimer les espaces et les tabulations à la fin de chaque ligne. Textabulous !\",\n      \"title\": \"Qu'est-ce qu'un outil de jointure de texte ?\"\n    }\n  },\n  \"palindrome\": {\n    \"description\": \"L'utilitaire le plus simple au monde, basé sur un navigateur, permet de vérifier si un texte est un palindrome. Vérifiez instantanément si votre texte se lit de la même manière à l'endroit comme à l'envers. Idéal pour les jeux de mots, l'analyse linguistique ou la validation de structures de texte symétriques. Prend en charge divers délimiteurs et la détection de palindromes multi-mots.\",\n    \"shortDescription\": \"Vérifiez si le texte se lit de la même manière à l'avant et à l'arrière\",\n    \"title\": \"Palindrome\"\n  },\n  \"passwordGenerator\": {\n    \"avoidAmbiguous\": \"Évitez les caractères ambigus (i, I, l, 0, O)\",\n    \"description\": \"Générez des mots de passe aléatoires sécurisés avec une longueur et des types de caractères personnalisables. Choisissez entre minuscules, majuscules, chiffres et caractères spéciaux. Option permettant d'éviter les caractères ambigus pour une meilleure lisibilité.\",\n    \"includeLowercase\": \"Inclure des lettres minuscules (a-z)\",\n    \"includeNumbers\": \"Inclure des chiffres (0-9)\",\n    \"includeSymbols\": \"Inclure des caractères spéciaux\",\n    \"includeUppercase\": \"Inclure des lettres majuscules (A-Z)\",\n    \"lengthDesc\": \"Longueur du mot de passe\",\n    \"lengthPlaceholder\": \"par exemple 12\",\n    \"optionsTitle\": \"Options de mot de passe\",\n    \"resultTitle\": \"Mot de passe généré\",\n    \"shortDescription\": \"Générez des mots de passe aléatoires sécurisés avec des options personnalisées\",\n    \"title\": \"Générateur de mot de passe\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil génère des mots de passe aléatoires sécurisés selon vos critères. Vous pouvez personnaliser la longueur, inclure ou exclure différents types de caractères et éviter les caractères ambigus pour une meilleure lisibilité. Idéal pour créer des mots de passe forts pour vos comptes, applications ou tout autre besoin de sécurité.\",\n      \"title\": \"À propos du générateur de mots de passe\"\n    }\n  },\n  \"quote\": {\n    \"allowDoubleQuotation\": \"Autoriser les guillemets doubles\",\n    \"description\": \"Ajoutez des guillemets autour du texte avec des options personnalisables.\",\n    \"inputTitle\": \"Texte d'entrée\",\n    \"leftQuoteDescription\": \"Caractère(s) de citation gauche\",\n    \"processAsMultiLine\": \"Traiter comme un texte multiligne\",\n    \"quoteEmptyLines\": \"Citer les lignes vides\",\n    \"quoteOptions\": \"Options de devis\",\n    \"resultTitle\": \"Texte cité\",\n    \"rightQuoteDescription\": \"Caractère(s) de citation à droite\",\n    \"shortDescription\": \"Ajoutez des guillemets autour du texte avec différents styles\",\n    \"title\": \"Citation de texte\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet d'insérer des guillemets autour du texte. Vous pouvez choisir différents caractères de guillemets, gérer du texte multiligne et contrôler le traitement des lignes vides. Il est utile pour préparer du texte pour la programmation, formater des données ou créer du texte stylisé.\",\n      \"title\": \"Citation de texte\"\n    }\n  },\n  \"randomizeCase\": {\n    \"description\": \"L'utilitaire de sélection aléatoire de casse de texte le plus simple au monde, basé sur un navigateur. Saisissez votre texte et transformez-le instantanément en ajoutant des majuscules et des minuscules aléatoires. Idéal pour créer des effets de texte uniques, tester la sensibilité à la casse ou générer des modèles de texte variés.\",\n    \"shortDescription\": \"Randomiser la casse des lettres dans le texte\",\n    \"title\": \"Cas randomisé\"\n  },\n  \"removeDuplicateLines\": {\n    \"description\": \"Chargez votre texte dans le formulaire de saisie à gauche et obtenez instantanément du texte sans doublons dans la zone de sortie. Puissant, gratuit et rapide. Chargez des lignes de texte : obtenez des lignes de texte uniques.\",\n    \"shortDescription\": \"Supprimez rapidement toutes les lignes répétées du texte\",\n    \"title\": \"Supprimer les lignes en double\"\n  },\n  \"repeat\": {\n    \"delimiterDescription\": \"Délimiteur pour les copies de sortie.\",\n    \"delimiterPlaceholder\": \"Délimiteur\",\n    \"description\": \"Répétez le texte plusieurs fois avec des séparateurs personnalisables.\",\n    \"inputTitle\": \"Texte d'entrée\",\n    \"numberPlaceholder\": \"Nombre\",\n    \"repeatAmountDescription\": \"Nombre de répétitions.\",\n    \"repetitionsDelimiter\": \"Délimiteur de répétitions\",\n    \"resultTitle\": \"Texte répété\",\n    \"shortDescription\": \"Répéter le texte plusieurs fois\",\n    \"textRepetitions\": \"Répétitions de texte\",\n    \"title\": \"Répéter le texte\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de répéter un texte donné plusieurs fois avec un séparateur optionnel.\",\n      \"title\": \"Répéter le texte\"\n    }\n  },\n  \"reverse\": {\n    \"description\": \"L'utilitaire d'inversion de texte le plus simple au monde, basé sur un navigateur. Saisissez n'importe quel texte et inversez-le instantanément, caractère par caractère. Idéal pour créer du texte miroir, analyser des palindromes ou jouer avec les motifs de texte. Préserve les espaces et les caractères spéciaux lors de l'inversion.\",\n    \"inputTitle\": \"Texte à inverser\",\n    \"processMultiLine\": \"Traiter un texte multiligne\",\n    \"processMultiLineDescription\": \"Chaque ligne sera inversée indépendamment\",\n    \"resultTitle\": \"Texte inversé\",\n    \"reversalOptions\": \"Options d'inversion\",\n    \"shortDescription\": \"Inverser n'importe quel texte caractère par caractère\",\n    \"skipEmptyLines\": \"Sauter les lignes vides\",\n    \"skipEmptyLinesDescription\": \"Les lignes vides seront supprimées de la sortie\",\n    \"title\": \"Inverse\",\n    \"trimWhitespace\": \"Couper les espaces blancs\",\n    \"trimWhitespaceDescription\": \"Supprimer les espaces de début et de fin de chaque ligne\"\n  },\n  \"rot13\": {\n    \"description\": \"Coder ou décoder du texte à l'aide du chiffrement ROT13.\",\n    \"inputTitle\": \"Texte d'entrée\",\n    \"resultTitle\": \"Résultat ROT13\",\n    \"shortDescription\": \"Coder ou décoder du texte à l'aide du chiffrement ROT13.\",\n    \"title\": \"Encodeur/décodeur ROT13\",\n    \"toolInfo\": {\n      \"description\": \"ROT13 (rotation de 13 positions) est un chiffrement par substitution de lettres simple qui remplace une lettre par la 13e lettre suivante dans l'alphabet. ROT13 est un cas particulier du chiffrement de César, développé dans la Rome antique. L'alphabet anglais comportant 26 lettres, ROT13 est son propre inverse ; pour annuler ROT13, le même algorithme est appliqué, permettant ainsi la même opération de codage et de décodage.\",\n      \"title\": \"Qu'est-ce que ROT13 ?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"Faire pivoter les caractères du texte selon des positions spécifiées.\",\n    \"inputTitle\": \"Texte d'entrée\",\n    \"processAsMultiLine\": \"Traiter comme un texte multiligne (faire pivoter chaque ligne séparément)\",\n    \"resultTitle\": \"Texte pivoté\",\n    \"rotateLeft\": \"Tourner à gauche\",\n    \"rotateRight\": \"Tourner à droite\",\n    \"rotationOptions\": \"Options de rotation\",\n    \"shortDescription\": \"Décaler les caractères dans le texte par position.\",\n    \"stepDescription\": \"Nombre de positions à faire tourner\",\n    \"title\": \"Faire pivoter le texte\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de faire pivoter les caractères d'une chaîne d'un nombre spécifié de positions. Vous pouvez effectuer une rotation vers la gauche ou la droite, et traiter du texte multiligne en faisant pivoter chaque ligne séparément. La rotation de chaîne est utile pour les transformations de texte simples, la création de motifs ou la mise en œuvre de techniques de chiffrement de base.\",\n      \"title\": \"Rotation des cordes\"\n    }\n  },\n  \"split\": {\n    \"charAfterChunkDescription\": \"Caractère après chaque morceau\",\n    \"charBeforeChunkDescription\": \"Caractère avant chaque morceau\",\n    \"chunksDescription\": \"Nombre de morceaux de longueur égale dans la sortie.\",\n    \"chunksTitle\": \"Utiliser un certain nombre de morceaux\",\n    \"description\": \"L'utilitaire de découpage de texte basé sur navigateur le plus simple au monde. Saisissez votre texte et spécifiez un séparateur pour le diviser en plusieurs parties. Idéal pour le traitement de données, la manipulation de texte ou l'extraction de contenu spécifique à partir de blocs de texte volumineux.\",\n    \"lengthDescription\": \"Nombre de symboles qui seront placés dans chaque bloc de sortie.\",\n    \"lengthTitle\": \"Utiliser la longueur pour le fractionnement\",\n    \"outputSeparatorDescription\": \"Caractère à insérer entre les segments séparés. (Par défaut, il s'agit du saut de ligne « \\\\n ».)\",\n    \"outputSeparatorOptions\": \"Options de séparateur de sortie\",\n    \"regexDescription\": \"Expression régulière permettant de diviser le texte en plusieurs parties.\\n(Plusieurs espaces par défaut.)\",\n    \"regexTitle\": \"Utiliser une expression régulière pour le fractionnement\",\n    \"resultTitle\": \"Textes\",\n    \"shortDescription\": \"Diviser le texte en plusieurs parties à l'aide d'un séparateur\",\n    \"splitSeparatorOptions\": \"Options de séparateur fractionné\",\n    \"symbolDescription\": \"Caractère permettant de diviser le texte en plusieurs parties.\\n(Espace par défaut.)\",\n    \"symbolTitle\": \"Utiliser un symbole pour le fractionnement\",\n    \"title\": \"Diviser\"\n  },\n  \"statistic\": {\n    \"characterFrequencyAnalysis\": \"Analyse de la fréquence des caractères\",\n    \"characterFrequencyAnalysisDescription\": \"Comptez la fréquence à laquelle chaque caractère apparaît dans le texte\",\n    \"delimitersOptions\": \"Options de délimiteurs\",\n    \"description\": \"Analysez le texte et générez des statistiques complètes.\",\n    \"includeEmptyLines\": \"Inclure les lignes vides\",\n    \"includeEmptyLinesDescription\": \"Inclure les lignes vides lors du comptage des lignes\",\n    \"inputTitle\": \"Texte d'entrée\",\n    \"resultTitle\": \"Statistiques textuelles\",\n    \"sentenceDelimitersDescription\": \"Saisissez les caractères personnalisés utilisés pour délimiter les phrases dans votre langue (séparés par une virgule) ou laissez-le vide par défaut.\",\n    \"sentenceDelimitersPlaceholder\": \"par exemple ., !, ?, ...\",\n    \"shortDescription\": \"Obtenez des statistiques sur votre texte\",\n    \"statisticsOptions\": \"Options de statistiques\",\n    \"title\": \"Statistiques textuelles\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet d'analyser du texte et de générer des statistiques complètes, notamment le nombre de caractères, le nombre de mots, le nombre de lignes et l'analyse de fréquence des caractères et des mots.\",\n      \"title\": \"Qu'est-ce qu'un {{title}}?\"\n    },\n    \"wordDelimitersDescription\": \"Saisissez une expression régulière personnalisée pour compter les mots ou laissez-la vide par défaut.\",\n    \"wordDelimitersPlaceholder\": \"par exemple \\\\s.,;:!?\\\"«»()…\",\n    \"wordFrequencyAnalysis\": \"Analyse de fréquence des mots\",\n    \"wordFrequencyAnalysisDescription\": \"Comptez la fréquence à laquelle chaque mot apparaît dans le texte\"\n  },\n  \"textReplacer\": {\n    \"description\": \"Remplacez les modèles de texte par un nouveau contenu.\",\n    \"findPatternInText\": \"Trouver ce modèle dans le texte\",\n    \"findPatternUsingRegexp\": \"Trouver un modèle à l'aide d'une expression régulière\",\n    \"inputTitle\": \"Texte à remplacer\",\n    \"newTextPlaceholder\": \"Nouveau texte\",\n    \"regexpDescription\": \"Saisissez l’expression régulière que vous souhaitez remplacer.\",\n    \"replacePatternDescription\": \"Saisissez le modèle à utiliser pour le remplacement.\",\n    \"replaceText\": \"Remplacer le texte\",\n    \"resultTitle\": \"Texte avec remplacements\",\n    \"searchPatternDescription\": \"Saisissez le modèle de texte que vous souhaitez remplacer.\",\n    \"searchText\": \"Texte de recherche\",\n    \"shortDescription\": \"Remplacez rapidement du texte dans votre contenu\",\n    \"title\": \"Remplaceur de texte\",\n    \"toolInfo\": {\n      \"description\": \"Remplacez facilement du texte spécifique dans votre contenu grâce à cet outil simple et accessible depuis un navigateur. Saisissez simplement votre texte, définissez le texte à remplacer et la valeur de remplacement, et obtenez instantanément la version mise à jour.\",\n      \"title\": \"Remplaceur de texte\"\n    }\n  },\n  \"toMorse\": {\n    \"dashSymbolDescription\": \"Symbole qui correspondra au tiret dans le code Morse.\",\n    \"description\": \"Convertir du texte en code Morse.\",\n    \"dotSymbolDescription\": \"Symbole qui correspondra au point dans le code Morse.\",\n    \"longSignal\": \"Signal long\",\n    \"resultTitle\": \"code Morse\",\n    \"shortDescription\": \"Encoder rapidement du texte en morse\",\n    \"shortSignal\": \"Signal court\",\n    \"title\": \"Chaîne en morse\"\n  },\n  \"truncate\": {\n    \"addTruncationIndicator\": \"Ajouter un indicateur de troncature\",\n    \"charactersPlaceholder\": \"Personnages\",\n    \"description\": \"Raccourcir le texte à une longueur spécifiée.\",\n    \"indicatorDescription\": \"Caractères à ajouter à la fin (ou au début) du texte. Remarque : ils sont pris en compte dans la longueur.\",\n    \"inputTitle\": \"Texte d'entrée\",\n    \"leftSideDescription\": \"Supprimer des caractères du début du texte.\",\n    \"leftSideTruncation\": \"Troncature du côté gauche\",\n    \"lengthAndLines\": \"Longueur et lignes\",\n    \"lineByLineDescription\": \"Tronquez chaque ligne séparément.\",\n    \"lineByLineTruncating\": \"Tronquer ligne par ligne\",\n    \"maxLengthDescription\": \"Nombre de caractères à laisser dans le texte.\",\n    \"numberPlaceholder\": \"Nombre\",\n    \"resultTitle\": \"Texte tronqué\",\n    \"rightSideDescription\": \"Supprimer les caractères de la fin du texte.\",\n    \"rightSideTruncation\": \"Troncature du côté droit\",\n    \"shortDescription\": \"Tronquer le texte à une longueur spécifiée\",\n    \"suffixAndAffix\": \"Suffixe et affixe\",\n    \"title\": \"Tronquer le texte\",\n    \"toolInfo\": {\n      \"description\": \"Chargez votre texte dans le formulaire de saisie à gauche et vous obtiendrez automatiquement du texte tronqué à droite.\",\n      \"title\": \"Tronquer le texte\"\n    },\n    \"truncationSide\": \"Côté troncature\"\n  },\n  \"uppercase\": {\n    \"description\": \"Convertir le texte en lettres majuscules.\",\n    \"inputTitle\": \"Texte d'entrée\",\n    \"resultTitle\": \"Texte en majuscules\",\n    \"shortDescription\": \"Convertir le texte en majuscules\",\n    \"title\": \"Convertir en majuscules\"\n  },\n  \"urlDecode\": {\n    \"inputTitle\": \"Chaîne d'entrée (URL échappée)\",\n    \"resultTitle\": \"Chaîne de sortie\",\n    \"toolInfo\": {\n      \"description\": \"Chargez votre chaîne et elle sera automatiquement sans échappement d'URL.\",\n      \"longDescription\": \"Cet outil décode l'URL d'une chaîne précédemment encodée. Le décodage d'URL est l'opération inverse de l'encodage d'URL. Tous les caractères encodés en pourcentage sont décodés en caractères compréhensibles. Parmi les valeurs encodées en pourcentage les plus connues, on trouve %20 pour un espace, %3a pour deux points, %2f pour une barre oblique et %3f pour un point d'interrogation. Les deux chiffres suivant le signe de pourcentage correspondent aux valeurs hexadécimales du caractère.\",\n      \"shortDescription\": \"Annulez rapidement l'échappement URL d'une chaîne.\",\n      \"title\": \"Décodeur d'URL de chaîne\"\n    }\n  },\n  \"urlEncode\": {\n    \"encodingOption\": {\n      \"nonSpecialCharDescription\": \"Si cette option est sélectionnée, tous les caractères de la chaîne d'entrée seront convertis en codage URL (pas seulement spécial).\",\n      \"nonSpecialCharPlaceholder\": \"Encoder les caractères non spéciaux\",\n      \"title\": \"Options d'encodage\"\n    },\n    \"inputTitle\": \"Chaîne d'entrée\",\n    \"resultTitle\": \"Chaîne d'échappement d'URL\",\n    \"toolInfo\": {\n      \"description\": \"Chargez votre chaîne et elle sera automatiquement échappée par URL.\",\n      \"longDescription\": \"Cet outil encode une chaîne en URL. Les caractères d'URL spéciaux sont convertis en pourcentage. Ce codage est appelé « encodage en pourcentage » car la valeur numérique de chaque caractère est convertie en pourcentage suivi d'une valeur hexadécimale à deux chiffres. Les valeurs hexadécimales sont déterminées en fonction de la valeur du point de code du caractère. Par exemple, un espace est converti en %20, deux points en %3a et une barre oblique en %2f. Les caractères non spéciaux restent inchangés. Si vous devez également convertir des caractères non spéciaux en pourcentage, nous avons ajouté une option supplémentaire. Sélectionnez l'option « encode-non-special-chars » pour activer ce comportement.\",\n      \"shortDescription\": \"Échappez rapidement une chaîne par URL.\",\n      \"title\": \"Encodeur d'URL de chaîne\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/time.json",
    "content": "{\n  \"checkLeapYears\": {\n    \"description\": \"Vérifiez si une année est bissextile et obtenez des informations sur les années bissextiles.\",\n    \"exampleDescription\": \"Une de nos amies est née le 29 février, une année bissextile. Par conséquent, son anniversaire n'est célébré qu'une fois tous les quatre ans. Comme nous ne nous souvenons jamais vraiment de sa date d'anniversaire, nous utilisons notre programme pour créer une liste de rappel des prochaines années bissextiles. Pour cela, nous chargeons une séquence d'années de 2025 à 2040 dans l'entrée et obtenons le statut de chaque année. Si le programme indique qu'il s'agit d'une année bissextile, nous savons que nous serons invités à une fête d'anniversaire le 29 février.\",\n    \"exampleTitle\": \"Trouver les anniversaires du 29 février\",\n    \"inputTitle\": \"Année d'entrée\",\n    \"resultTitle\": \"Résultat de l'année bissextile\",\n    \"shortDescription\": \"Vérifiez si une année est bissextile\",\n    \"title\": \"Vérifiez les années bissextiles\",\n    \"toolInfo\": {\n      \"description\": \"Une année bissextile est une année comportant un jour supplémentaire (le 29 février) pour synchroniser l'année civile avec l'année astronomique. Les années bissextiles ont lieu tous les 4 ans, sauf pour les années divisibles par 100 mais pas par 400.\",\n      \"title\": \"Qu'est-ce qu'une année bissextile ?\"\n    }\n  },\n  \"convertDaysToHours\": {\n    \"addHoursName\": \"Ajouter le nom des heures\",\n    \"addHoursNameDescription\": \"Ajoutez la chaîne hours aux valeurs de sortie\",\n    \"description\": \"Convertissez les jours en heures avec des options personnalisables.\",\n    \"hoursName\": \"Nom des heures\",\n    \"shortDescription\": \"Convertir des jours en heures\",\n    \"title\": \"Convertir des jours en heures\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de convertir des jours en heures. Vous pouvez saisir les jours sous forme de nombres ou d'unités, et l'outil les convertira en heures. Vous pouvez également ajouter le suffixe « heures » aux valeurs de sortie.\",\n      \"title\": \"Convertir des jours en heures\"\n    }\n  },\n  \"convertHoursToDays\": {\n    \"addDaysName\": \"Ajouter le nom du jour\",\n    \"addDaysNameDescription\": \"Ajoutez la chaîne days aux valeurs de sortie\",\n    \"daysName\": \"Nom des jours\",\n    \"description\": \"Convertissez les heures en jours avec des options personnalisables.\",\n    \"shortDescription\": \"Convertir des heures en jours\",\n    \"title\": \"Convertir des heures en jours\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de convertir des heures en jours. Vous pouvez saisir des heures sous forme de nombres ou d'unités, et l'outil les convertira en jours. Vous pouvez également ajouter le suffixe « jours » aux valeurs de sortie.\",\n      \"title\": \"Convertir des heures en jours\"\n    }\n  },\n  \"convertSecondsToTime\": {\n    \"addPadding\": \"Ajouter un rembourrage\",\n    \"addPaddingDescription\": \"Ajoutez un remplissage de zéro aux heures, minutes et secondes.\",\n    \"description\": \"Convertissez les secondes dans un format horaire lisible (heures:minutes:secondes). Saisissez le nombre de secondes pour obtenir l'heure formatée.\",\n    \"shortDescription\": \"Convertir les secondes au format horaire\",\n    \"timePadding\": \"Remplissage temporel\",\n    \"title\": \"Convertir les secondes en temps\",\n    \"toolInfo\": {\n      \"title\": \"Qu'est-ce qu'un {{title}}?\"\n    }\n  },\n  \"convertTimeToSeconds\": {\n    \"description\": \"Convertissez l'heure formatée (HH:MM:SS) en secondes.\",\n    \"inputTitle\": \"Heure d'entrée\",\n    \"resultTitle\": \"Secondes\",\n    \"shortDescription\": \"Convertir le format de l'heure en secondes\",\n    \"title\": \"Convertir le temps en secondes\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil permet de convertir des chaînes de temps formatées (HH:MM:SS) en secondes. Il est utile pour calculer des durées et des intervalles de temps.\",\n      \"title\": \"Convertir le temps en secondes\"\n    }\n  },\n  \"crontabGuru\": {\n    \"description\": \"Générez et comprenez les expressions Cron. Créez des planifications Cron pour les tâches automatisées et les tâches système.\",\n    \"shortDescription\": \"Générer et comprendre les expressions cron\",\n    \"title\": \"Crontab Guru\"\n  },\n  \"timeBetweenDates\": {\n    \"description\": \"Calculez le décalage horaire entre deux dates. Obtenez la durée exacte en jours, heures, minutes et secondes.\",\n    \"endDate\": \"Date de fin\",\n    \"endDateTime\": \"Date et heure de fin\",\n    \"endTime\": \"Fin des temps\",\n    \"endTimezone\": \"Fuseau horaire de fin\",\n    \"shortDescription\": \"Calculer le temps entre deux dates\",\n    \"startDate\": \"Date de début\",\n    \"startDateTime\": \"Date et heure de début\",\n    \"startTime\": \"Heure de début\",\n    \"startTimezone\": \"Fuseau horaire de départ\",\n    \"title\": \"Temps entre les dates\",\n    \"toolInfo\": {\n      \"description\": \"Calculez le décalage horaire exact entre deux dates et heures, avec la prise en charge de différents fuseaux horaires. Cet outil fournit une analyse détaillée du décalage horaire en différentes unités (années, mois, jours, heures, minutes et secondes).\",\n      \"title\": \"Calculateur de temps entre les dates\"\n    }\n  },\n  \"truncateClockTime\": {\n    \"description\": \"Tronquez l'heure pour supprimer les secondes ou les minutes. Arrondissez l'heure à l'heure, à la minute ou à l'intervalle personnalisé le plus proche.\",\n    \"printDroppedComponents\": \"Imprimer les composants supprimés\",\n    \"shortDescription\": \"Tronquer l'heure de l'horloge à la précision spécifiée\",\n    \"timePadding\": \"Remplissage temporel\",\n    \"title\": \"Tronquer l'heure de l'horloge\",\n    \"toolInfo\": {\n      \"title\": \"Qu'est-ce qu'un {{title}}?\"\n    },\n    \"truncateMinutesAndSeconds\": \"Tronquer les minutes et les secondes\",\n    \"truncateMinutesAndSecondsDescription\": \"Supprimez les deux composants – les minutes et les secondes – de chaque heure d’horloge.\",\n    \"truncateOnlySeconds\": \"Tronquer uniquement les secondes\",\n    \"truncateOnlySecondsDescription\": \"Supprimez le composant secondes de chaque heure d'horloge.\",\n    \"truncationSide\": \"Côté troncature\",\n    \"useZeroPadding\": \"Utiliser le remplissage zéro\",\n    \"zeroPaddingDescription\": \"Faites en sorte que tous les composants de temps aient toujours une largeur de deux chiffres.\",\n    \"zeroPrintDescription\": \"Afficher les parties supprimées sous forme de valeurs nulles « 00 ».\",\n    \"zeroPrintTruncatedParts\": \"Parties tronquées sans impression\"\n  },\n  \"convertUnixToDate\": {\n    \"title\": \"Convertir un timestamp Unix en date\",\n    \"description\": \"Convertit un timestamp Unix en une date lisible par un humain.\",\n    \"shortDescription\": \"Conversion de timestamp Unix en date\",\n    \"longDescription\": \"Cet outil permet de convertir un timestamp Unix (en secondes) en une date lisible au format AAAA-MM-JJ HH:MM:SS. Il prend en charge l'affichage en UTC ou dans le fuseau horaire local, ce qui est pratique pour interpréter rapidement des horodatages issus de journaux, d'API ou de systèmes utilisant le temps Unix.\",\n    \"withLabel\": \"Options\",\n    \"outputOptions\": \"Options de sortie\",\n    \"addUtcLabel\": \"Ajouter le suffixe 'UTC'\",\n    \"addUtcLabelDescription\": \"Affiche 'UTC' après la date convertie (uniquement en mode UTC)\",\n    \"useLocalTime\": \"Utiliser l’heure locale\",\n    \"useLocalTimeDescription\": \"Affiche la date convertie dans votre fuseau horaire local au lieu de l’heure UTC\",\n    \"toolInfo\": {\n      \"title\": \"Convertir un timestamp Unix en date\",\n      \"description\": \"Cet outil convertit un timestamp Unix (en secondes) en une date lisible (par ex. AAAA-MM-JJ HH:MM:SS). Il prend en charge l'affichage en heure locale ou en UTC, ce qui le rend utile pour analyser rapidement des données issues de journaux ou d’APIs.\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/translation.json",
    "content": "{\n  \"audio\": {\n    \"changeSpeed\": {\n      \"description\": \"Modifier la vitesse de lecture des fichiers audio. Accélérer ou ralentir le son tout en conservant la hauteur.\",\n      \"name\": \"Changer la vitesse audio\",\n      \"shortDescription\": \"Modifier la vitesse des fichiers audio\"\n    },\n    \"extractAudio\": {\n      \"description\": \"Extrayez la piste audio d'un fichier vidéo et enregistrez-la en tant que fichier audio séparé dans le format de votre choix (AAC, MP3, WAV).\",\n      \"name\": \"Extraire l'audio\",\n      \"shortDescription\": \"Extrayez l'audio des fichiers vidéo (MP4, MOV, etc.) vers AAC, MP3 ou WAV.\"\n    }\n  },\n  \"baseFileInput\": {\n    \"copyFailed\": \"Échec de la copie : {{error}}\",\n    \"dropFileHere\": \"Déposez votre {{type}} ici\",\n    \"fileCopied\": \"Fichier copié\",\n    \"selectFileDescription\": \"Cliquez ici pour sélectionner un {{type}} depuis votre appareil, appuyez sur Ctrl+V pour utiliser un {{type}} depuis votre presse-papiers ou faites glisser et déposez un fichier depuis le bureau\"\n  },\n  \"categories\": {\n    \"audio\": {\n      \"description\": \"Outils pour travailler avec l'audio : extraire l'audio d'une vidéo, ajuster la vitesse de l'audio, fusionner plusieurs fichiers audio et bien plus encore.\",\n      \"title\": \"Outils audio\"\n    },\n    \"csv\": {\n      \"description\": \"Outils pour travailler avec des fichiers CSV : convertissez des fichiers CSV en différents formats, manipulez les données CSV, validez la structure CSV et traitez efficacement les fichiers CSV.\",\n      \"title\": \"Outils CSV\"\n    },\n    \"gif\": {\n      \"description\": \"Outils pour travailler avec des animations GIF : créez des GIF transparents, extrayez des cadres GIF, ajoutez du texte au GIF, recadrez, faites pivoter, inversez les GIF et bien plus encore.\",\n      \"title\": \"Outils GIF\"\n    },\n    \"image-generic\": {\n      \"description\": \"Outils pour travailler avec des images : compresser, redimensionner, recadrer, convertir en JPG, faire pivoter, supprimer l'arrière-plan et bien plus encore.\",\n      \"title\": \"Outils d'image\"\n    },\n    \"json\": {\n      \"description\": \"Outils pour travailler avec des structures de données JSON : embellir et minimiser des objets JSON, aplatir des tableaux JSON, transformer des valeurs JSON en chaînes, analyser des données et bien plus encore\",\n      \"title\": \"Outils JSON\"\n    },\n    \"list\": {\n      \"description\": \"Outils pour travailler avec des listes : trier, inverser, randomiser les listes, trouver des éléments de liste uniques et en double, modifier les séparateurs d'éléments de liste et bien plus encore.\",\n      \"title\": \"Liste des outils\"\n    },\n    \"number\": {\n      \"description\": \"Outils pour travailler avec des nombres : générer des séquences de nombres, convertir des nombres en mots et des mots en nombres, trier, arrondir, factoriser des nombres et bien plus encore.\",\n      \"title\": \"Outils numériques\"\n    },\n    \"pdf\": {\n      \"description\": \"Outils pour travailler avec des fichiers PDF : extraire du texte à partir de PDF, convertir des PDF en d'autres formats, manipuler des PDF et bien plus encore.\",\n      \"title\": \"Outils PDF\"\n    },\n    \"png\": {\n      \"description\": \"Outils pour travailler avec des images PNG : convertissez des PNG en JPG, créez des PNG transparents, modifiez les couleurs PNG, recadrez, faites pivoter, redimensionnez des PNG et bien plus encore.\",\n      \"title\": \"Outils PNG\"\n    },\n    \"seeAll\": \"Voir les {{title}}\",\n    \"string\": {\n      \"description\": \"Outils pour travailler avec du texte : convertissez du texte en images, recherchez et remplacez du texte, divisez du texte en fragments, joignez des lignes de texte, répétez du texte et bien plus encore.\",\n      \"title\": \"Outils de texte\"\n    },\n    \"time\": {\n      \"description\": \"Outils pour travailler avec l'heure et la date : calculez les différences horaires, convertissez entre les fuseaux horaires, formatez les dates, générez des séquences de dates et bien plus encore.\",\n      \"title\": \"Outils de temps\"\n    },\n    \"try\": \"Essayer {{title}}\",\n    \"video\": {\n      \"description\": \"Outils pour travailler avec des vidéos : extraire des images de vidéos, créer des GIF à partir de vidéos, convertir des vidéos en différents formats et bien plus encore.\",\n      \"title\": \"Outils vidéo\"\n    },\n    \"xml\": {\n      \"description\": \"Outils pour travailler avec des structures de données XML - visualiseur, embellisseur, validateur et bien plus encore\",\n      \"title\": \"Outils XML\"\n    }\n  },\n  \"csv\": {\n    \"findIncompleteCsvRecords\": {\n      \"description\": \"Il vous suffit de télécharger votre fichier CSV dans le formulaire ci-dessous et cet outil vérifiera automatiquement qu'aucune ligne ou colonne ne contient de valeur manquante. Dans les options de l'outil, vous pouvez ajuster le format du fichier d'entrée (spécifier le délimiteur, les guillemets et les commentaires). De plus, vous pouvez activer la vérification des valeurs vides, ignorer les lignes vides et limiter le nombre de messages d'erreur dans la sortie.\",\n      \"name\": \"Rechercher des enregistrements CSV incomplets\",\n      \"shortDescription\": \"Trouvez rapidement les lignes et les colonnes dans CSV auxquelles il manque des valeurs.\"\n    }\n  },\n  \"hero\": {\n    \"brand\": \"OmniTools\",\n    \"description\": \"Boostez votre productivité avec OmniTools, la boîte à outils ultime pour accélérer vos tâches ! Accédez à des milliers d'utilitaires conviviaux pour modifier des images, du texte, des listes et des données, directement depuis votre navigateur.\",\n    \"examples\": {\n      \"calculateNumberSum\": \"Calculer la somme des nombres\",\n      \"changeGifSpeed\": \"Modifier la vitesse du GIF\",\n      \"compressPng\": \"Compresser PNG\",\n      \"createTransparentImage\": \"Créer une image transparente\",\n      \"prettifyJson\": \"Embellir JSON\",\n      \"sortList\": \"Trier une liste\",\n      \"splitPdf\": \"Diviser le PDF\",\n      \"splitText\": \"Diviser un texte\",\n      \"trimVideo\": \"Découper la vidéo\"\n    },\n    \"searchPlaceholder\": \"Rechercher tous les outils\",\n    \"title\": \"Faites avancer les choses rapidement avec\"\n  },\n  \"inputFooter\": {\n    \"clear\": \"Supprimer les fichiers\",\n    \"copyToClipboard\": \"Copier dans le presse-papiers\",\n    \"importFromFile\": \"Importer à partir d'un fichier\"\n  },\n  \"list\": {\n    \"group\": {\n      \"description\": \"L'utilitaire de regroupement d'éléments de liste le plus simple au monde, accessible depuis un navigateur. Saisissez votre liste et spécifiez des critères de regroupement pour organiser les éléments en groupes logiques. Idéal pour catégoriser des données, organiser des informations ou créer des listes structurées. Prend en charge les séparateurs personnalisés et diverses options de regroupement.\",\n      \"name\": \"Groupe\",\n      \"shortDescription\": \"Regrouper les éléments de la liste par propriétés communes\"\n    },\n    \"reverse\": {\n      \"description\": \"Cette application très simple, basée sur un navigateur, imprime tous les éléments d'une liste à l'envers. Les éléments saisis peuvent être séparés par n'importe quel symbole et vous pouvez également modifier le séparateur des éléments inversés.\",\n      \"name\": \"Inverse\",\n      \"shortDescription\": \"Inverser rapidement une liste\"\n    },\n    \"sort\": {\n      \"description\": \"Il s'agit d'une application navigateur ultra-simple qui trie les éléments d'une liste et les organise par ordre croissant ou décroissant. Vous pouvez trier les éléments par ordre alphabétique, numérique ou par longueur. Vous pouvez également supprimer les doublons et les éléments vides, ainsi que les éléments individuels entourés d'espaces. Vous pouvez utiliser n'importe quel caractère séparateur pour séparer les éléments de la liste d'entrée ou une expression régulière. De plus, vous pouvez créer un nouveau délimiteur pour la liste de sortie triée.\",\n      \"name\": \"Trier\",\n      \"shortDescription\": \"Trier rapidement une liste\"\n    }\n  },\n  \"navbar\": {\n    \"buyMeACoffee\": \"Offre-moi un café\",\n    \"hireMe\": \"Embauchez-moi\",\n    \"home\": \"Maison\",\n    \"tools\": \"Outils\"\n  },\n  \"number\": {\n    \"generate\": {\n      \"description\": \"Calculez rapidement une liste d'entiers dans votre navigateur. Pour obtenir votre liste, indiquez simplement le premier entier, modifiez la valeur et le nombre total dans les options ci-dessous, et cet utilitaire générera le nombre d'entiers correspondant.\",\n      \"name\": \"Générer des nombres\",\n      \"shortDescription\": \"Calculez rapidement une liste d'entiers dans votre navigateur\"\n    },\n    \"sum\": {\n      \"description\": \"Il s'agit d'une application très simple, accessible depuis un navigateur, qui additionne des nombres. Les nombres saisis peuvent être séparés par n'importe quel symbole et vous pouvez également modifier le séparateur des nombres additionnés.\",\n      \"name\": \"Somme des nombres\",\n      \"shortDescription\": \"Additionner rapidement une liste de nombres\"\n    }\n  },\n  \"numericInputWithUnit\": {\n    \"unit\": \"Unité\"\n  },\n  \"pdf\": {\n    \"compressPdf\": {\n      \"description\": \"Réduire la taille des fichiers PDF tout en maintenant la qualité grâce à Ghostscript\",\n      \"name\": \"Compresser le PDF\",\n      \"shortDescription\": \"Compresser des fichiers PDF en toute sécurité dans votre navigateur\"\n    },\n    \"mergePdf\": {\n      \"description\": \"Comment utiliser l'outil Fusionner PDF ?\",\n      \"name\": \"Fusionner des PDF\",\n      \"shortDescription\": \"Fusionner plusieurs fichiers PDF en un seul document\"\n    },\n    \"pdfToEpub\": {\n      \"description\": \"Transformez les documents PDF en fichiers EPUB pour une meilleure compatibilité avec les liseuses.\",\n      \"name\": \"PDF vers EPUB\",\n      \"shortDescription\": \"Convertir des fichiers PDF au format EPUB\"\n    },\n    \"protectPdf\": {\n      \"description\": \"Ajoutez une protection par mot de passe à vos fichiers PDF en toute sécurité dans votre navigateur\",\n      \"name\": \"Protéger le PDF\",\n      \"shortDescription\": \"Protégez les fichiers PDF en toute sécurité avec un mot de passe\"\n    },\n    \"splitPdf\": {\n      \"description\": \"Extraire des pages spécifiques d'un fichier PDF à l'aide de numéros de page ou de plages (par exemple, 1, 5-8)\",\n      \"name\": \"Diviser le PDF\",\n      \"shortDescription\": \"Extraire des pages spécifiques d'un fichier PDF\"\n    }\n  },\n  \"resultFooter\": {\n    \"copy\": \"Copier dans le presse-papiers\",\n    \"download\": \"Télécharger\"\n  },\n  \"string\": {\n    \"createPalindrome\": {\n      \"description\": \"L'utilitaire de navigateur le plus simple au monde pour créer des palindromes à partir de n'importe quel texte. Saisissez du texte et transformez-le instantanément en un palindrome qui se lit de la même manière à l'endroit comme à l'envers. Idéal pour les jeux de mots, la création de motifs de texte symétriques ou l'exploration de curiosités linguistiques.\",\n      \"name\": \"Créer un palindrome\",\n      \"shortDescription\": \"Créez un texte qui se lit de la même manière à l'avant et à l'arrière\"\n    },\n    \"palindrome\": {\n      \"description\": \"L'utilitaire le plus simple au monde, basé sur un navigateur, permet de vérifier si un texte est un palindrome. Vérifiez instantanément si votre texte se lit de la même manière à l'endroit comme à l'envers. Idéal pour les jeux de mots, l'analyse linguistique ou la validation de structures de texte symétriques. Prend en charge divers délimiteurs et la détection de palindromes multi-mots.\",\n      \"name\": \"Palindrome\",\n      \"shortDescription\": \"Vérifiez si le texte se lit de la même manière à l'avant et à l'arrière\"\n    },\n    \"repeat\": {\n      \"description\": \"Cet outil vous permet de répéter un texte donné plusieurs fois avec un séparateur optionnel.\",\n      \"name\": \"Répéter le texte\",\n      \"shortDescription\": \"Répéter le texte plusieurs fois\"\n    },\n    \"reverse\": {\n      \"description\": \"L'utilitaire d'inversion de texte le plus simple au monde, basé sur un navigateur. Saisissez n'importe quel texte et inversez-le instantanément, caractère par caractère. Idéal pour créer du texte miroir, analyser des palindromes ou jouer avec les motifs de texte. Préserve les espaces et les caractères spéciaux lors de l'inversion.\",\n      \"name\": \"Inverse\",\n      \"shortDescription\": \"Inverser n'importe quel texte caractère par caractère\"\n    },\n    \"toMorse\": {\n      \"description\": \"L'utilitaire de conversion de texte en code Morse le plus simple au monde, accessible depuis un navigateur. Chargez votre texte dans le formulaire de saisie à gauche et obtenez instantanément le code Morse dans la zone de sortie. Puissant, gratuit et rapide. Chargez du texte et obtenez le code Morse.\",\n      \"name\": \"Chaîne en morse\",\n      \"shortDescription\": \"Encoder rapidement du texte en morse\"\n    },\n    \"uppercase\": {\n      \"description\": \"L'utilitaire de conversion de texte en majuscules le plus simple au monde, accessible depuis un navigateur. Saisissez simplement votre texte et il sera automatiquement converti en majuscules. Idéal pour créer des titres, mettre en valeur du texte ou standardiser son format. Prend en charge divers formats de texte et préserve les caractères spéciaux.\",\n      \"name\": \"Majuscule\",\n      \"shortDescription\": \"Convertir le texte en lettres majuscules\"\n    }\n  },\n  \"toolExamples\": {\n    \"subtitle\": \"Cliquez pour essayer !\",\n    \"title\": \"{{title}} Exemples\"\n  },\n  \"toolFileResult\": {\n    \"copied\": \"Fichier copié\",\n    \"copyFailed\": \"Échec de la copie : {{error}}\",\n    \"loading\": \"Chargement... Cela peut prendre un moment.\",\n    \"result\": \"Résultat\"\n  },\n  \"toolHeader\": {\n    \"seeExamples\": \"Voir des exemples\"\n  },\n  \"toolLayout\": {\n    \"allToolsTitle\": \"Tous les {{type}}\"\n  },\n  \"toolMultiFileResult\": {\n    \"copied\": \"Fichier copié\",\n    \"copyFailed\": \"Échec de la copie : {{error}}\",\n    \"loading\": \"Chargement... Cela peut prendre un moment.\",\n    \"result\": \"Résultat\"\n  },\n  \"toolMultipleAudioInput\": {\n    \"inputTitle\": \"Saisir {{type}}\",\n    \"noFilesSelected\": \"Aucun fichier sélectionné\"\n  },\n  \"toolMultiplePdfInput\": {\n    \"inputTitle\": \"Saisir {{type}}\",\n    \"noFilesSelected\": \"Aucun fichier sélectionné\"\n  },\n  \"toolOptions\": {\n    \"title\": \"Options d'outils\"\n  },\n  \"toolTextInput\": {\n    \"copied\": \"Texte copié\",\n    \"copyFailed\": \"Échec de la copie : {{error}}\",\n    \"input\": \"Texte d'entrée\",\n    \"placeholder\": \"Entrez votre texte ici...\"\n  },\n  \"toolTextResult\": {\n    \"copied\": \"Texte copié\",\n    \"copyFailed\": \"Échec de la copie : {{error}}\",\n    \"loading\": \"Chargement... Cela peut prendre un moment.\",\n    \"result\": \"Résultat\"\n  },\n  \"userTypes\": {\n    \"developers\": \"Développeurs\",\n    \"generalUsers\": \"Grand public\"\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/video.json",
    "content": "{\n  \"changeSpeed\": {\n    \"defaultMultiplier\": \"Multiplicateur par défaut : 2 signifie 2x plus rapide\",\n    \"description\": \"Modifiez la vitesse de lecture des fichiers vidéo. Accélérez ou ralentissez les vidéos tout en conservant la synchronisation audio. Prise en charge de divers multiplicateurs de vitesse et formats vidéo courants.\",\n    \"inputTitle\": \"Entrée vidéo\",\n    \"newVideoSpeed\": \"Nouvelle vitesse vidéo\",\n    \"resultTitle\": \"Vidéo éditée\",\n    \"settingSpeed\": \"Réglage de la vitesse\",\n    \"shortDescription\": \"Modifier la vitesse de lecture vidéo\",\n    \"title\": \"Modifier la vitesse de la vidéo\",\n    \"toolInfo\": {\n      \"title\": \"Qu'est-ce qu'un {{title}}?\"\n    }\n  },\n  \"compress\": {\n    \"default\": \"Défaut\",\n    \"description\": \"Compressez vos vidéos en les adaptant à différentes résolutions (240p, 480p, 720p, etc.). Cet outil permet de réduire la taille des fichiers tout en conservant une qualité acceptable. Il prend en charge les formats vidéo courants comme MP4, WebM et OGG.\",\n    \"inputTitle\": \"Entrée vidéo\",\n    \"loadingText\": \"Compression vidéo...\",\n    \"lossless\": \"Sans perte\",\n    \"quality\": \"Qualité (CRF)\",\n    \"resolution\": \"Résolution\",\n    \"resultTitle\": \"Vidéo compressée\",\n    \"shortDescription\": \"Compresser des vidéos en les mettant à l'échelle vers différentes résolutions\",\n    \"title\": \"Compresser la vidéo\",\n    \"worst\": \"Pire\"\n  },\n  \"cropVideo\": {\n    \"cropCoordinates\": \"Coordonnées de culture\",\n    \"croppingVideo\": \"Recadrage vidéo\",\n    \"description\": \"Recadrez la vidéo pour supprimer les zones indésirables.\",\n    \"errorBeyondHeight\": \"La zone de recadrage s'étend au-delà de la hauteur de la vidéo ({{height}}px)\",\n    \"errorBeyondWidth\": \"La zone de recadrage s'étend au-delà de la largeur de la vidéo ({{width}}px)\",\n    \"errorCroppingVideo\": \"Erreur lors du recadrage de la vidéo. Veuillez vérifier les paramètres et le fichier vidéo.\",\n    \"errorLoadingDimensions\": \"Échec du chargement des dimensions de la vidéo\",\n    \"errorNonNegativeCoordinates\": \"Les coordonnées X et Y doivent être non négatives\",\n    \"errorPositiveDimensions\": \"La largeur et la hauteur doivent être positives\",\n    \"height\": \"Hauteur\",\n    \"inputTitle\": \"Entrée vidéo\",\n    \"loadVideoForDimensions\": \"Chargez une vidéo pour voir les dimensions\",\n    \"longDescription\": \"Cet outil vous permet de recadrer des fichiers vidéo pour supprimer les zones indésirables ou vous concentrer sur des parties spécifiques. Il est utile pour supprimer les barres noires, ajuster les proportions ou se concentrer sur le contenu important. Il prend en charge divers formats vidéo, dont MP4, MOV et AVI.\",\n    \"resultTitle\": \"Vidéo recadrée\",\n    \"shortDescription\": \"Recadrer la vidéo pour supprimer les zones indésirables\",\n    \"title\": \"Recadrer la vidéo\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de recadrer des fichiers vidéo afin de supprimer les zones indésirables. Vous pouvez spécifier la zone de recadrage en définissant les coordonnées X et Y, ainsi que les dimensions de largeur et de hauteur.\",\n      \"title\": \"Recadrer la vidéo\"\n    },\n    \"videoDimensions\": \"Dimensions de la vidéo : {{width}} × {{height}} pixels\",\n    \"videoInformation\": \"Informations vidéo\",\n    \"width\": \"Largeur\",\n    \"xCoordinate\": \"X (gauche)\",\n    \"yCoordinate\": \"Y (en haut)\"\n  },\n  \"flip\": {\n    \"description\": \"Retournez les fichiers vidéo horizontalement ou verticalement. Inversez les vidéos pour obtenir des effets spéciaux ou corriger les problèmes d'orientation.\",\n    \"flippingVideo\": \"Retournement vidéo\",\n    \"horizontalLabel\": \"Horizontal (miroir)\",\n    \"inputTitle\": \"Entrée vidéo\",\n    \"orientation\": \"Orientation\",\n    \"resultTitle\": \"Vidéo inversée\",\n    \"shortDescription\": \"Retourner la vidéo horizontalement ou verticalement\",\n    \"title\": \"Retourner la vidéo\",\n    \"verticalLabel\": \"Vertical (à l'envers)\"\n  },\n  \"gif\": {\n    \"changeSpeed\": {\n      \"description\": \"Modifiez la vitesse de lecture des animations GIF. Accélérez ou ralentissez les GIF tout en conservant une animation fluide.\",\n      \"shortDescription\": \"Modifier la vitesse de l'animation GIF\",\n      \"title\": \"Modifier la vitesse du GIF\"\n    }\n  },\n  \"loop\": {\n    \"description\": \"Créez une vidéo en boucle en répétant la vidéo originale plusieurs fois.\",\n    \"inputTitle\": \"Entrée vidéo\",\n    \"loopingVideo\": \"Vidéo en boucle\",\n    \"loops\": \"Boucles\",\n    \"numberOfLoops\": \"Nombre de boucles\",\n    \"resultTitle\": \"Vidéo en boucle\",\n    \"shortDescription\": \"Créer des fichiers vidéo en boucle\",\n    \"title\": \"Vidéo en boucle\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de créer une vidéo en boucle en répétant la vidéo originale plusieurs fois. Vous pouvez spécifier le nombre de répétitions.\",\n      \"title\": \"Qu'est-ce qu'un {{title}}?\"\n    }\n  },\n  \"mergeVideo\": {\n    \"description\": \"Combinez plusieurs fichiers vidéo en une seule vidéo continue.\",\n    \"longDescription\": \"Cet outil vous permet de fusionner ou d'ajouter plusieurs fichiers vidéo en une seule vidéo continue. Téléchargez simplement vos fichiers vidéo, organisez-les dans l'ordre souhaité et fusionnez-les en un seul fichier pour un partage ou un montage simplifiés.\",\n    \"shortDescription\": \"Ajoutez et fusionnez facilement des vidéos.\",\n    \"title\": \"Fusionner des vidéos\"\n  },\n  \"rotate\": {\n    \"180Degrees\": \"180° (à l'envers)\",\n    \"270Degrees\": \"270° (90° dans le sens inverse des aiguilles d'une montre)\",\n    \"90Degrees\": \"90° dans le sens des aiguilles d'une montre\",\n    \"description\": \"Faites pivoter vos fichiers vidéo de 90, 180 ou 270 degrés. Corrigez l'orientation de vos vidéos ou créez des effets spéciaux grâce à un contrôle précis de la rotation.\",\n    \"inputTitle\": \"Entrée vidéo\",\n    \"resultTitle\": \"Vidéo tournée\",\n    \"rotatingVideo\": \"Vidéo rotative\",\n    \"rotation\": \"Rotation\",\n    \"shortDescription\": \"Faire pivoter la vidéo selon les degrés spécifiés\",\n    \"title\": \"Faire pivoter la vidéo\"\n  },\n  \"trim\": {\n    \"description\": \"Découpez vos fichiers vidéo en spécifiant les heures de début et de fin. Supprimez les sections indésirables au début ou à la fin de vos vidéos.\",\n    \"endTime\": \"Fin des temps\",\n    \"inputTitle\": \"Entrée vidéo\",\n    \"resultTitle\": \"Vidéo coupée\",\n    \"shortDescription\": \"Coupez la vidéo en supprimant les sections indésirables\",\n    \"startTime\": \"Heure de début\",\n    \"timestamps\": \"Horodatages\",\n    \"title\": \"Découper la vidéo\"\n  },\n  \"videoToGif\": {\n    \"description\": \"Convertissez des fichiers vidéo au format GIF animé. Extrayez des plages horaires spécifiques et créez des images animées partageables.\",\n    \"shortDescription\": \"Convertir une vidéo en GIF animé\",\n    \"title\": \"Vidéo en GIF\"\n  }\n}\n"
  },
  {
    "path": "public/locales/fr/xml.json",
    "content": "{\n  \"xmlBeautifier\": {\n    \"description\": \"Formater XML avec une indentation et un espacement appropriés.\",\n    \"indentation\": \"Indentation\",\n    \"inputTitle\": \"XML d'entrée\",\n    \"resultTitle\": \"XML embelli\",\n    \"shortDescription\": \"Formater et embellir le code XML\",\n    \"title\": \"Embellisseur XML\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de formater les données XML avec une indentation et un espacement appropriés, les rendant plus lisibles et plus faciles à utiliser.\",\n      \"title\": \"Embellisseur XML\"\n    },\n    \"useSpaces\": \"Utiliser les espaces\",\n    \"useSpacesDescription\": \"Indenter la sortie avec des espaces\",\n    \"useTabs\": \"Utiliser les onglets\",\n    \"useTabsDescription\": \"Indenter la sortie avec des tabulations.\"\n  },\n  \"xmlValidator\": {\n    \"description\": \"Valider la syntaxe et la structure XML.\",\n    \"placeholder\": \"Collez ou importez du XML ici...\",\n    \"shortDescription\": \"Valider le code XML pour les erreurs\",\n    \"title\": \"Validateur XML\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de valider la syntaxe et la structure XML. Il vérifie la bonne formation du XML et fournit des messages d'erreur détaillés pour tout problème détecté.\",\n      \"title\": \"Validateur XML\"\n    }\n  },\n  \"xmlViewer\": {\n    \"description\": \"Afficher et explorer la structure XML dans un format arborescent.\",\n    \"inputTitle\": \"XML d'entrée\",\n    \"resultTitle\": \"Vue arborescente du XML\",\n    \"title\": \"Visionneuse XML\",\n    \"toolInfo\": {\n      \"description\": \"Cet outil vous permet de visualiser les données XML dans un format d'arborescence hiérarchique, ce qui facilite l'exploration et la compréhension de la structure des documents XML.\",\n      \"title\": \"Visionneuse XML\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/audio.json",
    "content": "{\n  \"changeSpeed\": {\n    \"description\": \"ऑडियो फ़ाइलों की प्लेबैक गति बदलें।\",\n    \"factorPlaceholder\": \"कारक (जैसे 0.5, 1.5, 2.0)\",\n    \"formatAac\": \"AAC\",\n    \"formatMp3\": \"MP3\",\n    \"formatWav\": \"WAV\",\n    \"inputTitle\": \"इनपुट ऑडियो\",\n    \"newAudioSpeed\": \"नई ऑडियो गति\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"preservePitch\": \"पिच संरक्षित करें\",\n    \"resultTitle\": \"गति बदली गई ऑडियो\",\n    \"settingSpeed\": \"गति सेट करना\",\n    \"shortDescription\": \"ऑडियो फ़ाइलों की गति बदलें\",\n    \"speedDescription\": \"डिफ़ॉल्ट गुणक: 2 का अर्थ है 2x तेज़\",\n    \"speedFactor\": \"गति कारक\",\n    \"speedOptions\": \"गति विकल्प\",\n    \"title\": \"ऑडियो गति बदलें\",\n    \"toolInfo\": {\n      \"title\": \"क्या है {{title}}?\"\n    }\n  },\n  \"extractAudio\": {\n    \"audioFormat\": \"ऑडियो प्रारूप\",\n    \"audioQuality\": \"ऑडियो गुणवत्ता\",\n    \"description\": \"वीडियो फ़ाइल से ऑडियो ट्रैक निकालें।\",\n    \"extractAllTracks\": \"सभी ट्रैक निकालें\",\n    \"extractingAudio\": \"ऑडियो निकालना\",\n    \"extractionOptions\": \"निकालने के विकल्प\",\n    \"formatAac\": \"AAC\",\n    \"formatMp3\": \"MP3\",\n    \"formatWav\": \"WAV\",\n    \"inputTitle\": \"इनपुट वीडियो\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"outputFormatDescription\": \"निकाले जाने वाले ऑडियो के लिए प्रारूप का चयन करें।\",\n    \"qualityHigh\": \"उच्च\",\n    \"qualityLow\": \"कम\",\n    \"qualityMedium\": \"मध्यम\",\n    \"resultTitle\": \"निकाला गया ऑडियो\",\n    \"shortDescription\": \"वीडियो फ़ाइलों (एमपी4, एमओवी, आदि) से ऑडियो निकालें एएसी, एमपी3, या डब्ल्यूएवी में।\",\n    \"title\": \"ऑडियो निकालें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको वीडियो फ़ाइलों से ऑडियो ट्रैक निकालने की सुविधा देता है। आप AAC, MP3 और WAV सहित विभिन्न ऑडियो फ़ॉर्मेट में से चुन सकते हैं।\",\n      \"title\": \"क्या है {{title}}?\"\n    }\n  },\n  \"mergeAudio\": {\n    \"description\": \"कई ऑडियो फ़ाइलों को एक में जोड़ें।\",\n    \"formatAac\": \"AAC\",\n    \"formatMp3\": \"MP3\",\n    \"formatWav\": \"WAV\",\n    \"inputTitle\": \"इनपुट ऑडियो फ़ाइलें\",\n    \"longDescription\": \"यह टूल आपको कई ऑडियो फ़ाइलों को अपलोड करने के क्रम में जोड़कर उन्हें एक ही फ़ाइल में मर्ज करने की सुविधा देता है। पॉडकास्ट सेगमेंट, म्यूज़िक ट्रैक या किसी भी ऑडियो फ़ाइल को एक साथ जोड़ने के लिए यह बिल्कुल सही है। MP3, AAC और WAV सहित विभिन्न ऑडियो फ़ॉर्मेट को सपोर्ट करता है।\",\n    \"mergeMethod\": \"मर्ज विधि\",\n    \"mergeOptions\": \"मर्ज विकल्प\",\n    \"mergingAudio\": \"ऑडियो मर्ज करना\",\n    \"methodConcat\": \"क्रमबद्ध जोड़ना\",\n    \"methodMix\": \"मिश्रण\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"resultTitle\": \"मर्ज किया गया ऑडियो\",\n    \"shortDescription\": \"एकाधिक ऑडियो फ़ाइलों को एक में मर्ज करें (MP3, AAC, WAV)।\",\n    \"title\": \"ऑडियो मर्ज करें\",\n    \"toolInfo\": {\n      \"title\": \"क्या है {{title}}?\"\n    }\n  },\n  \"trim\": {\n    \"description\": \"ऑडियो फ़ाइल से अनावश्यक भाग हटाएं।\",\n    \"endPlaceholder\": \"सेकंड\",\n    \"endTime\": \"अंतिम समय\",\n    \"endTimeDescription\": \"समाप्ति समय HH:MM:SS प्रारूप में (उदाहरणार्थ, 00:01:30)\",\n    \"fadeIn\": \"फेड इन\",\n    \"fadeInPlaceholder\": \"सेकंड\",\n    \"fadeOut\": \"फेड आउट\",\n    \"fadeOutPlaceholder\": \"सेकंड\",\n    \"formatMp3\": \"MP3\",\n    \"formatWav\": \"WAV\",\n    \"inputTitle\": \"इनपुट ऑडियो\",\n    \"longDescription\": \"यह टूल आपको ऑडियो फ़ाइलों को शुरू और खत्म होने का समय निर्दिष्ट करके ट्रिम करने की सुविधा देता है। आप लंबी ऑडियो फ़ाइलों से विशिष्ट खंड निकाल सकते हैं, अवांछित हिस्से हटा सकते हैं, या छोटी क्लिप बना सकते हैं। MP3, AAC, और WAV सहित विभिन्न ऑडियो फ़ॉर्मेट को सपोर्ट करता है। पॉडकास्ट एडिटिंग, म्यूज़िक प्रोडक्शन, या किसी भी ऑडियो एडिटिंग ज़रूरत के लिए बिल्कुल सही।\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"resultTitle\": \"ट्रिम किया गया ऑडियो\",\n    \"shortDescription\": \"विशिष्ट समय खंडों (MP3, AAC, WAV) को निकालने के लिए ऑडियो फ़ाइलों को ट्रिम करें।\",\n    \"startPlaceholder\": \"सेकंड\",\n    \"startTime\": \"शुरुआती समय\",\n    \"startTimeDescription\": \"प्रारंभ समय HH:MM:SS प्रारूप में (उदाहरणार्थ, 00:00:30)\",\n    \"timeSettings\": \"समय सैट करना\",\n    \"title\": \"ऑडियो ट्रिम करें\",\n    \"toolInfo\": {\n      \"title\": \"क्या है {{title}}?\"\n    },\n    \"trimOptions\": \"ट्रिम विकल्प\",\n    \"trimmingAudio\": \"ऑडियो ट्रिमिंग\"\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/converters.json",
    "content": "{\n  \"audioConverter\": {\n    \"title\": \"ऑडियो कनवर्टर\",\n    \"description\": \"ऑडियो फ़ाइलों को विभिन्न प्रारूपों के बीच रूपांतरित करें।\",\n    \"shortDescription\": \"ऑडियो फ़ाइलों को विभिन्न प्रारूपों में रूपांतरित करें।\",\n    \"longDescription\": \"यह उपकरण आपको ऑडियो फ़ाइलों को एक प्रारूप से दूसरे में रूपांतरित करने की अनुमति देता है, निरंतर रूपांतरण के लिए ऑडियो प्रारूपों की एक विस्तृत श्रृंखला का समर्थन करता है।\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"outputFormatDescription\": \"वांछित आउटपुट ऑडियो प्रारूप का चयन करें\",\n    \"inputTitle\": \"ऑडियो इनपुट\",\n    \"outputTitle\": \"परिवर्तित ऑडियो\"\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/csv.json",
    "content": "{\n  \"changeCsvSeparator\": {\n    \"description\": \"CSV फ़ाइलों में सीमांकक/विभाजक बदलें। विभिन्न CSV प्रारूपों जैसे अल्पविराम, अर्धविराम, टैब या कस्टम विभाजकों के बीच रूपांतरण करें।\",\n    \"shortDescription\": \"CSV फ़ाइल सीमांकक बदलें\",\n    \"title\": \"CSV विभाजक बदलें\"\n  },\n  \"changeSeparator\": {\n    \"comma\": \"कॉमा\",\n    \"commonSeparators\": \"सामान्य विभाजक\",\n    \"description\": \"CSV फ़ाइल में विभाजक वर्ण बदलें।\",\n    \"inputSeparator\": \"इनपुट विभाजक\",\n    \"inputSeparatorPlaceholder\": \"विभाजक\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"outputSeparator\": \"आउटपुट विभाजक\",\n    \"outputSeparatorPlaceholder\": \"विभाजक\",\n    \"pipe\": \"पाइप\",\n    \"resultTitle\": \"परिवर्तित CSV\",\n    \"semicolon\": \"सेमीकोलन\",\n    \"separatorOptions\": \"विभाजक विकल्प\",\n    \"tab\": \"टैब\",\n    \"title\": \"CSV विभाजक बदलें\"\n  },\n  \"csvRowsToColumns\": {\n    \"description\": \"यह टूल CSV (कॉमा सेपरेटेड वैल्यूज़) फ़ाइल की पंक्तियों को कॉलम में परिवर्तित करता है। यह इनपुट CSV से क्षैतिज रेखाओं को एक-एक करके निकालता है, उन्हें 90 डिग्री घुमाता है, और उन्हें कॉमा द्वारा अलग किए गए एक के बाद एक लंबवत कॉलम के रूप में आउटपुट करता है।', longDescription: 'यह टूल CSV (कॉमा सेपरेटेड वैल्यूज़) फ़ाइल की पंक्तियों को कॉलम में परिवर्तित करता है। उदाहरण के लिए, यदि इनपुट CSV डेटा में 6 पंक्तियाँ हैं, तो आउटपुट में भी 6 कॉलम होंगे और पंक्तियों के तत्व ऊपर से नीचे की ओर व्यवस्थित होंगे। एक सुव्यवस्थित CSV में, प्रत्येक पंक्ति में मानों की संख्या समान होती है। हालाँकि, यदि पंक्तियों में फ़ील्ड गायब हैं, तो प्रोग्राम उन्हें ठीक कर सकता है और आप उपलब्ध विकल्पों में से चुन सकते हैं: गायब डेटा को खाली तत्वों से भरें या गायब डेटा को कस्टम तत्वों, जैसे \\\"missing\\\", \\\"?\\\", या \\\"x\\\" से बदलें। रूपांतरण प्रक्रिया के दौरान, यह टूल CSV फ़ाइल से अनावश्यक जानकारी, जैसे खाली पंक्तियाँ (ये दृश्यमान जानकारी रहित पंक्तियाँ हैं) और टिप्पणियाँ, भी साफ़ करता है। टूल को टिप्पणियों की सही पहचान करने में मदद करने के लिए, विकल्पों में, आप टिप्पणी शुरू करने वाली पंक्ति की शुरुआत में प्रतीक निर्दिष्ट कर सकते हैं। यह प्रतीक आमतौर पर एक हैश \\\"#\\\" या डबल स्लैश \\\"//\\\" होता है। Csv-abulous!.\",\n    \"longDescription\": \"यह टूल CSV (कॉमा सेपरेटेड वैल्यूज़) फ़ाइल की पंक्तियों को कॉलम में बदलता है। उदाहरण के लिए, यदि इनपुट CSV डेटा में 6 पंक्तियाँ हैं, तो आउटपुट में भी 6 कॉलम होंगे और पंक्तियों के तत्व ऊपर से नीचे की ओर व्यवस्थित होंगे। एक सुव्यवस्थित CSV में, प्रत्येक पंक्ति में मानों की संख्या समान होती है। हालाँकि, जब पंक्तियों में फ़ील्ड गायब हों, तो प्रोग्राम उन्हें ठीक कर सकता है और आप उपलब्ध विकल्पों में से चुन सकते हैं: खाली तत्वों से गायब डेटा भरें या कस्टम तत्वों से गायब डेटा को बदलें, जैसे\",\n    \"shortDescription\": \"CSV पंक्तियों को स्तंभों में परिवर्तित करें.\",\n    \"title\": \"CSV पंक्तियों को स्तंभों में परिवर्तित करें\"\n  },\n  \"csvToJson\": {\n    \"arrayFormat\": \"सरणी प्रारूप\",\n    \"columnSeparator\": \"स्तंभ विभाजक (उदाहरणार्थ, , ; \\\\t)\",\n    \"commentSymbol\": \"टिप्पणी प्रतीक (उदाहरणार्थ, #)\",\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"description\": \"CSV डेटा को JSON प्रारूप में बदलें।\",\n    \"dynamicTypes\": \"गतिशील प्रकार\",\n    \"dynamicTypesDescription\": \"संख्याओं और बूलियन को स्वचालित रूप से परिवर्तित करें\",\n    \"error\": \"गलती\",\n    \"errorParsing\": \"CSV पार्स करने में त्रुटि: {{error}}\",\n    \"fieldQuote\": \"फ़ील्ड उद्धरण (उदाहरणार्थ, \\\")\",\n    \"firstRowAsHeaders\": \"पहली पंक्ति शीर्षक के रूप में\",\n    \"includeHeaders\": \"शीर्षक शामिल करें\",\n    \"inputCsvFormat\": \"इनपुट CSV प्रारूप\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"invalidCsvFormat\": \"अमान्य CSV प्रारूप\",\n    \"objectFormat\": \"ऑब्जेक्ट प्रारूप\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"resultTitle\": \"JSON परिणाम\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"CSV डेटा को JSON प्रारूप में परिवर्तित करें.\",\n    \"skipEmptyLines\": \"खाली लाइनें छोड़ें\",\n    \"skipEmptyLinesDescription\": \"इनपुट CSV में रिक्त पंक्तियों को अनदेखा करें\",\n    \"title\": \"CSV से JSON\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल कॉमा सेपरेटेड वैल्यूज़ (CSV) फ़ाइलों को जावास्क्रिप्ट ऑब्जेक्ट नोटेशन (JSON) डेटा स्ट्रक्चर में बदल देता है। यह अनुकूलन योग्य डिलीमीटर, कोट कैरेक्टर और कमेंट सिंबल के साथ विभिन्न CSV फ़ॉर्मैट को सपोर्ट करता है। यह कन्वर्टर पहली पंक्ति को हेडर के रूप में इस्तेमाल कर सकता है, खाली लाइनों को छोड़ सकता है, और संख्याओं और बूलियन जैसे डेटा प्रकारों का स्वचालित रूप से पता लगा सकता है। परिणामी JSON का उपयोग डेटा माइग्रेशन, बैकअप या अन्य अनुप्रयोगों के लिए इनपुट के रूप में किया जा सकता है।\",\n      \"title\": \"CSV से JSON कनवर्टर क्या है?\"\n    },\n    \"useHeaders\": \"हेडर का उपयोग करें\",\n    \"useHeadersDescription\": \"पहली पंक्ति को स्तंभ शीर्षक के रूप में मानें\"\n  },\n  \"csvToTsv\": {\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"description\": \"CSV फ़ाइल को TSV प्रारूप में बदलें।\",\n    \"inputSeparator\": \"इनपुट विभाजक\",\n    \"inputSeparatorPlaceholder\": \"विभाजक\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"longDescription\": \"यह टूल कॉमा सेपरेटेड वैल्यूज़ (CSV) डेटा को टैब सेपरेटेड वैल्यूज़ (TSV) डेटा में बदल देता है। CSV और TSV दोनों ही सारणीबद्ध डेटा संग्रहीत करने के लिए लोकप्रिय फ़ाइल स्वरूप हैं, लेकिन वे मानों को अलग करने के लिए अलग-अलग डिलीमीटर का उपयोग करते हैं - CSV कॉमा (\",\n    \"preserveHeaders\": \"शीर्षक संरक्षित करें\",\n    \"resultTitle\": \"TSV परिणाम\",\n    \"shortDescription\": \"CSV डेटा को TSV प्रारूप में परिवर्तित करें.\",\n    \"title\": \"CSV से TSV\"\n  },\n  \"csvToXml\": {\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"description\": \"CSV डेटा को XML प्रारूप में बदलें।\",\n    \"firstRowAsHeaders\": \"पहली पंक्ति शीर्षक के रूप में\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"resultTitle\": \"XML परिणाम\",\n    \"rootElement\": \"मूल तत्व\",\n    \"rootPlaceholder\": \"तत्व नाम\",\n    \"rowElement\": \"पंक्ति तत्व\",\n    \"rowPlaceholder\": \"तत्व नाम\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"CSV डेटा को XML प्रारूप में परिवर्तित करें.\",\n    \"title\": \"CSV से XML\"\n  },\n  \"csvToYaml\": {\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"description\": \"CSV डेटा को YAML प्रारूप में बदलें।\",\n    \"firstRowAsHeaders\": \"पहली पंक्ति शीर्षक के रूप में\",\n    \"includeHeaders\": \"शीर्षक शामिल करें\",\n    \"indentSize\": \"इंडेंट आकार\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"longDescription\": \"यह टूल CSV (कॉमा सेपरेटेड वैल्यूज़) डेटा को YAML (येट अनदर मार्कअप लैंग्वेज) डेटा में बदल देता है। CSV एक सरल, सारणीबद्ध फ़ॉर्मैट है जिसका उपयोग पंक्तियों और स्तंभों वाले मैट्रिक्स जैसे डेटा प्रकारों को दर्शाने के लिए किया जाता है। दूसरी ओर, YAML एक अधिक उन्नत फ़ॉर्मैट (वास्तव में JSON का एक सुपरसेट) है, जो क्रमांकन के लिए अधिक मानव-पठनीय डेटा बनाता है, और यह सूचियों, शब्दकोशों और नेस्टेड ऑब्जेक्ट्स का समर्थन करता है। यह प्रोग्राम विभिन्न इनपुट CSV फ़ॉर्मैट का समर्थन करता है - इनपुट डेटा कॉमा सेपरेटेड (डिफ़ॉल्ट), सेमीकोलन सेपरेटेड, पाइप सेपरेटेड हो सकता है, या किसी अन्य पूरी तरह से अलग डिलीमीटर का उपयोग कर सकता है। आप विकल्पों में अपने डेटा द्वारा उपयोग किए जाने वाले सटीक डिलीमीटर को निर्दिष्ट कर सकते हैं। इसी प्रकार, विकल्पों में, आप CSV फ़ील्ड को रैप करने के लिए उपयोग किए जाने वाले उद्धरण चिह्न को निर्दिष्ट कर सकते हैं (डिफ़ॉल्ट रूप से एक डबल-कोट प्रतीक)। आप विकल्पों में टिप्पणी चिह्न निर्दिष्ट करके टिप्पणियों से शुरू होने वाली पंक्तियों को भी छोड़ सकते हैं। इससे आप अनावश्यक पंक्तियों को छोड़कर अपने डेटा को साफ़ रख सकते हैं। CSV को YAML में बदलने के दो तरीके हैं। पहली विधि प्रत्येक CSV पंक्ति को एक YAML सूची में बदल देती है। दूसरी विधि पहली CSV पंक्ति से हेडर निकालती है और इन हेडर पर आधारित कुंजियों वाले YAML ऑब्जेक्ट बनाती है। आप YAML संरचनाओं को इंडेंट करने के लिए रिक्त स्थान की संख्या निर्दिष्ट करके आउटपुट YAML प्रारूप को भी अनुकूलित कर सकते हैं। यदि आपको रिवर्स रूपांतरण, यानी YAML को CSV में बदलने की आवश्यकता है, तो आप हमारे YAML को CSV में बदलने वाले टूल का उपयोग कर सकते हैं। Csv-abulous!\",\n    \"resultTitle\": \"YAML परिणाम\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"किसी CSV फ़ाइल को शीघ्रता से YAML फ़ाइल में परिवर्तित करें।\",\n    \"sizePlaceholder\": \"आकार\",\n    \"title\": \"CSV से YAML\"\n  },\n  \"findIncompleteCsvRecords\": {\n    \"checkingOptions\": \"विकल्पों की जाँच\",\n    \"commentCharacterDescription\": \"टिप्पणी पंक्ति की शुरुआत दर्शाने वाला वर्ण दर्ज करें। इस चिह्न से शुरू होने वाली पंक्तियाँ छोड़ दी जाएँगी।\",\n    \"csvInputOptions\": \"CSV इनपुट विकल्प\",\n    \"csvSeparatorDescription\": \"CSV इनपुट फ़ाइल में कॉलम को सीमांकित करने के लिए प्रयुक्त वर्ण दर्ज करें.\",\n    \"deleteLinesWithNoData\": \"बिना डेटा वाली पंक्तियाँ हटाएँ\",\n    \"deleteLinesWithNoDataDescription\": \"CSV इनपुट फ़ाइल से रिक्त पंक्तियाँ हटाएँ.\",\n    \"description\": \"बस नीचे फॉर्म में अपनी सीएसवी फ़ाइल अपलोड करें और यह टूल स्वचालित रूप से जांच करेगा कि क्या कोई पंक्ति या स्तंभ मूल्य नहीं खो रहे हैं। टूल विकल्पों में, आप इनपुट फ़ाइल प्रारूप को समायोजित कर सकते हैं (विभाजक, उद्धरण वर्ण, और टिप्पणी वर्ण निर्दिष्ट करें)। इसके अतिरिक्त, आप खाली मूल्यों की जांच सक्षम कर सकते हैं, खाली पंक्तियों को छोड़ सकते हैं, और आउटपुट में त्रुटि संदेशों की संख्या पर सीमा निर्धारित कर सकते हैं।\",\n    \"findEmptyValues\": \"रिक्त मान खोजें\",\n    \"findEmptyValuesDescription\": \"रिक्त CSV फ़ील्ड के बारे में संदेश प्रदर्शित करें (ये रिक्त फ़ील्ड नहीं हैं, बल्कि वे फ़ील्ड हैं जिनमें कुछ भी नहीं है)।\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"limitNumberOfMessages\": \"संदेशों की संख्या सीमित करें\",\n    \"messageLimitDescription\": \"आउटपुट में संदेशों की संख्या की सीमा निर्धारित करें.\",\n    \"quoteCharacterDescription\": \"CSV इनपुट फ़ील्ड को उद्धृत करने के लिए प्रयुक्त उद्धरण वर्ण दर्ज करें.\",\n    \"resultTitle\": \"सीएसवी स्थिति\",\n    \"shortDescription\": \"सीएसवी में जल्दी से पंक्तियां और स्तंभ खोजें जो मूल्य खो रहे हैं।\",\n    \"title\": \"अधूरे सीएसवी रिकॉर्ड खोजें\",\n    \"toolInfo\": {\n      \"title\": \"{{title}} क्या है?\"\n    }\n  },\n  \"findIncompleteRecords\": {\n    \"checkEmptyValues\": \"खाली मूल्य जांचें\",\n    \"description\": \"CSV फ़ाइल में अधूरे रिकॉर्ड खोजें।\",\n    \"errorLimit\": \"त्रुटि सीमा\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"limitPlaceholder\": \"सीमा\",\n    \"resultTitle\": \"अधूरे रिकॉर्ड\",\n    \"searchOptions\": \"खोज विकल्प\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"skipEmptyLines\": \"खाली पंक्तियां छोड़ें\",\n    \"title\": \"अधूरे CSV रिकॉर्ड खोजें\"\n  },\n  \"insertColumns\": {\n    \"columnNames\": \"स्तंभ नाम\",\n    \"defaultValues\": \"डिफ़ॉल्ट मूल्य\",\n    \"description\": \"CSV फ़ाइल में नए स्तंभ डालें।\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"insertPosition\": \"डालने की स्थिति\",\n    \"insertionOptions\": \"डालने के विकल्प\",\n    \"namesPlaceholder\": \"नाम (कॉमा से अलग)\",\n    \"positionPlaceholder\": \"स्थिति\",\n    \"resultTitle\": \"संशोधित CSV\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"title\": \"CSV स्तंभ डालें\",\n    \"valuesPlaceholder\": \"मूल्य (कॉमा से अलग)\"\n  },\n  \"insertCsvColumns\": {\n    \"appendColumns\": \"कॉलम जोड़ें\",\n    \"commentCharacterDescription\": \"टिप्पणी पंक्ति की शुरुआत दर्शाने वाला वर्ण दर्ज करें। इस चिह्न से शुरू होने वाली पंक्तियाँ छोड़ दी जाएँगी।\",\n    \"csvOptions\": \"CSV विकल्प\",\n    \"csvSeparator\": \"CSV विभाजक\",\n    \"csvToInsert\": \"सम्मिलित करने के लिए CSV\",\n    \"csvToInsertDescription\": \"CSV में डालने के लिए एक या एक से ज़्यादा कॉलम डालें। कॉलम को सीमांकित करने के लिए इस्तेमाल किया गया वर्ण CSV इनपुट फ़ाइल में मौजूद वर्ण के समान होना चाहिए। Ps: खाली पंक्तियों को अनदेखा कर दिया जाएगा।\",\n    \"customFillDescription\": \"यदि इनपुट CSV फ़ाइल अपूर्ण है (मान गायब हैं), तो एक सुव्यवस्थित CSV बनाने के लिए रिकॉर्ड में रिक्त फ़ील्ड या कस्टम प्रतीक जोड़ें?\",\n    \"customFillValueDescription\": \"रिक्त फ़ील्ड भरने के लिए इस कस्टम मान का उपयोग करें। (यह केवल ऊपर दिए गए \\\"कस्टम मान\\\" मोड के साथ काम करता है।)\",\n    \"customPosition\": \"कस्टम स्थिति\",\n    \"customPositionOptionsDescription\": \"CSV फ़ाइल में कॉलम सम्मिलित करने की विधि का चयन करें.\",\n    \"description\": \"निर्दिष्ट स्थानों पर CSV डेटा में नए कॉलम जोड़ें.\",\n    \"fillWithCustomValues\": \"सीमा शुल्क मूल्यों से भरें\",\n    \"fillWithEmptyValues\": \"रिक्त मानों से भरें\",\n    \"headerName\": \"हेडर का नाम\",\n    \"headerNameDescription\": \"उस कॉलम का शीर्षलेख जिसके बाद आप कॉलम सम्मिलित करना चाहते हैं.\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"insertingPositionDescription\": \"निर्दिष्ट करें कि CSV फ़ाइल में कॉलम कहाँ सम्मिलित करना है.\",\n    \"position\": \"पद\",\n    \"positionOptions\": \"स्थिति विकल्प\",\n    \"prependColumns\": \"कॉलम जोड़ें\",\n    \"quoteCharDescription\": \"CSV इनपुट फ़ील्ड को उद्धृत करने के लिए प्रयुक्त उद्धरण वर्ण दर्ज करें.\",\n    \"resultTitle\": \"आउटपुट CSV\",\n    \"rowNumberDescription\": \"उस कॉलम की संख्या जिसके बाद आप कॉलम सम्मिलित करना चाहते हैं.\",\n    \"separatorDescription\": \"CSV इनपुट फ़ाइल में कॉलम को सीमांकित करने के लिए प्रयुक्त वर्ण दर्ज करें.\",\n    \"shortDescription\": \"CSV फ़ाइल में कहीं भी एक या अधिक नए कॉलम शीघ्रता से डालें.\",\n    \"title\": \"CSV कॉलम डालें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको CSV डेटा में निर्दिष्ट स्थानों पर नए कॉलम डालने की सुविधा देता है। आप हेडर नामों या कॉलम संख्याओं के आधार पर कस्टम स्थानों पर कॉलम जोड़, जोड़ या डाल सकते हैं।\",\n      \"title\": \"CSV कॉलम डालें\"\n    }\n  },\n  \"rowsToColumns\": {\n    \"description\": \"CSV डेटा को पंक्तियों से स्तंभों में बदलें।\",\n    \"includeHeaders\": \"शीर्षक शामिल करें\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"resultTitle\": \"परिवर्तित CSV\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"title\": \"CSV पंक्तियां से स्तंभ\",\n    \"transformationOptions\": \"परिवर्तन विकल्प\",\n    \"transposeData\": \"डेटा ट्रांसपोज़ करें\"\n  },\n  \"swapColumns\": {\n    \"description\": \"CSV फ़ाइल में स्तंभों की स्थिति बदलें।\",\n    \"firstColumn\": \"पहला स्तंभ\",\n    \"firstColumnPlaceholder\": \"स्तंभ संख्या\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"resultTitle\": \"संशोधित CSV\",\n    \"secondColumn\": \"दूसरा स्तंभ\",\n    \"secondColumnPlaceholder\": \"स्तंभ संख्या\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"swapOptions\": \"बदलने के विकल्प\",\n    \"title\": \"CSV स्तंभ बदलें\"\n  },\n  \"swapCsvColumns\": {\n    \"description\": \"बस नीचे दिए गए फ़ॉर्म में अपनी CSV फ़ाइल अपलोड करें, स्वैप करने के लिए कॉलम निर्दिष्ट करें, और टूल स्वचालित रूप से आउटपुट फ़ाइल में निर्दिष्ट कॉलम की स्थिति बदल देगा। टूल विकल्पों में, आप उन कॉलम की स्थिति या नाम निर्दिष्ट कर सकते हैं जिन्हें आप स्वैप करना चाहते हैं, साथ ही अधूरे डेटा को ठीक कर सकते हैं और वैकल्पिक रूप से खाली रिकॉर्ड और टिप्पणी किए गए रिकॉर्ड हटा सकते हैं।\",\n    \"longDescription\": \"यह टूल CSV डेटा के कॉलम की स्थिति बदलकर उसे पुनर्व्यवस्थित करता है। कॉलम बदलने से अक्सर इस्तेमाल होने वाले डेटा को एक साथ या आगे रखकर CSV फ़ाइल की पठनीयता बढ़ाई जा सकती है, जिससे डेटा की तुलना और संपादन आसान हो जाता है। उदाहरण के लिए, आप पहले कॉलम को आखिरी कॉलम से या दूसरे कॉलम को तीसरे कॉलम से बदल सकते हैं। कॉलम की स्थिति के आधार पर उन्हें बदलने के लिए, चुनें\",\n    \"shortDescription\": \"CSV कॉलम पुनःक्रमित करें.\",\n    \"title\": \"CSV कॉलम स्वैप करें\"\n  },\n  \"transposeCsv\": {\n    \"description\": \"CSV डेटा को ट्रांसपोज़ करें (पंक्तियों को स्तंभों में बदलें)।\",\n    \"includeHeaders\": \"शीर्षक शामिल करें\",\n    \"inputTitle\": \"इनपुट CSV\",\n    \"longDescription\": \"यह टूल कॉमा सेपरेटेड वैल्यूज़ (CSV) को ट्रांसपोज़ करता है। यह CSV को डेटा के एक मैट्रिक्स की तरह मानता है और सभी तत्वों को मुख्य विकर्ण पर पलट देता है। आउटपुट में इनपुट के समान ही CSV डेटा होता है, लेकिन अब सभी पंक्तियाँ कॉलम में बदल गई हैं, और सभी कॉलम पंक्तियाँ में बदल गए हैं। ट्रांसपोज़िशन के बाद, CSV फ़ाइल के आयाम विपरीत होंगे। उदाहरण के लिए, यदि इनपुट फ़ाइल में 4 कॉलम और 3 पंक्तियाँ हैं, तो आउटपुट फ़ाइल में 3 कॉलम और 4 पंक्तियाँ होंगी। रूपांतरण के दौरान, प्रोग्राम अनावश्यक पंक्तियों से डेटा भी साफ़ करता है और अधूरे डेटा को सही करता है। विशेष रूप से, यह टूल किसी विशिष्ट वर्ण से शुरू होने वाले सभी रिक्त रिकॉर्ड और टिप्पणियों को स्वचालित रूप से हटा देता है, जिसे आप विकल्प में सेट कर सकते हैं। इसके अतिरिक्त, ऐसे मामलों में जहाँ CSV डेटा दूषित या खो गया है, उपयोगिता फ़ाइल को रिक्त फ़ील्ड या कस्टम फ़ील्ड से भर देती है जिन्हें विकल्पों में निर्दिष्ट किया जा सकता है। Csv-abulous!\",\n    \"resultTitle\": \"ट्रांसपोज़ किया गया CSV\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"CSV फ़ाइल को शीघ्रता से ट्रांसपोज़ करें.\",\n    \"title\": \"CSV ट्रांसपोज़ करें\",\n    \"transposeOptions\": \"ट्रांसपोज़ विकल्प\"\n  },\n  \"tsvToJson\": {\n    \"arrayFormat\": \"सरणी प्रारूप\",\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"description\": \"TSV डेटा को JSON प्रारूप में बदलें।\",\n    \"firstRowAsHeaders\": \"पहली पंक्ति शीर्षक के रूप में\",\n    \"includeHeaders\": \"शीर्षक शामिल करें\",\n    \"inputTitle\": \"इनपुट TSV\",\n    \"objectFormat\": \"ऑब्जेक्ट प्रारूप\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"resultTitle\": \"JSON परिणाम\",\n    \"shortDescription\": \"TSV को JSON प्रारूप में परिवर्तित करें\",\n    \"title\": \"TSV से JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/image.json",
    "content": "{\n  \"changeColors\": {\n    \"description\": \"दुनिया\",\n    \"shortDescription\": \"किसी छवि में रंगों को शीघ्रता से बदलें\",\n    \"title\": \"छवि में रंग बदलें\"\n  },\n  \"changeOpacity\": {\n    \"description\": \"अपनी छवियों की पारदर्शिता को आसानी से समायोजित करें। बस अपनी छवि अपलोड करें, स्लाइडर का उपयोग करके वांछित अपारदर्शिता स्तर 0 (पूरी तरह से पारदर्शी) और 1 (पूरी तरह से अपारदर्शी) के बीच सेट करें, और संशोधित छवि डाउनलोड करें।\",\n    \"shortDescription\": \"छवियों की पारदर्शिता समायोजित करें\",\n    \"title\": \"छवि अपारदर्शिता बदलें\"\n  },\n  \"compress\": {\n    \"compressedSize\": \"संपीड़ित आकार\",\n    \"compressionOptions\": \"संपीड़न विकल्प\",\n    \"description\": \"छवि फ़ाइल आकार कम करें।\",\n    \"failedToCompress\": \"छवि संपीड़ित करने में विफल. कृपया पुनः प्रयास करें.\",\n    \"fileSizes\": \"फ़ाइल आकार\",\n    \"formatJpeg\": \"JPEG\",\n    \"formatPng\": \"PNG\",\n    \"formatWebp\": \"WebP\",\n    \"imageQuality\": \"छवि गुणवत्ता\",\n    \"inputTitle\": \"इनपुट छवि\",\n    \"maxFileSizeDescription\": \"मेगाबाइट में अधिकतम फ़ाइल आकार\",\n    \"originalSize\": \"मूल आकार\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"progressiveJpeg\": \"प्रगतिशील JPEG\",\n    \"qualityDescription\": \"छवि गुणवत्ता प्रतिशत (कम का अर्थ है छोटा फ़ाइल आकार)\",\n    \"qualityPlaceholder\": \"गुणवत्ता (1-100)\",\n    \"removeMetadata\": \"मेटाडेटा हटाएं\",\n    \"resultTitle\": \"संपीड़ित छवि\",\n    \"shortDescription\": \"उचित गुणवत्ता बनाए रखते हुए फ़ाइल आकार को कम करने के लिए छवियों को संपीड़ित करें।\",\n    \"title\": \"छवि संपीड़ित करें\"\n  },\n  \"compressPng\": {\n    \"description\": \"यह एक ऐसा प्रोग्राम है जो PNG चित्रों को संपीड़ित करता है। जैसे ही आप अपनी PNG तस्वीर को इनपुट क्षेत्र में पेस्ट करेंगे, प्रोग्राम उसे संपीड़ित कर देगा और परिणाम आउटपुट क्षेत्र में दिखाएगा। विकल्पों में, आप संपीड़न स्तर को समायोजित कर सकते हैं, साथ ही पुरानी और नई तस्वीर फ़ाइल का आकार भी देख सकते हैं।\",\n    \"shortDescription\": \"PNG को शीघ्रता से संपीड़ित करें\",\n    \"title\": \"png संपीड़ित करें\"\n  },\n  \"convert\": {\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"description\": \"छवि को एक प्रारूप से दूसरे में बदलें।\",\n    \"formatBmp\": \"BMP\",\n    \"formatGif\": \"GIF\",\n    \"formatJpeg\": \"JPEG\",\n    \"formatPng\": \"PNG\",\n    \"formatTiff\": \"TIFF\",\n    \"formatWebp\": \"WebP\",\n    \"imageQuality\": \"छवि गुणवत्ता\",\n    \"inputTitle\": \"इनपुट छवि\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"preserveTransparency\": \"पारदर्शिता संरक्षित करें\",\n    \"qualityPlaceholder\": \"गुणवत्ता (1-100)\",\n    \"resultTitle\": \"परिवर्तित छवि\",\n    \"title\": \"छवि प्रारूप बदलें\"\n  },\n  \"convertJgpToPng\": {\n    \"description\": \"अपनी JPG इमेज को झटपट PNG में बदलें। बस अपनी PNG इमेज को बाईं ओर दिए गए एडिटर में इम्पोर्ट करें।\",\n    \"shortDescription\": \"अपनी JPG छवियों को शीघ्रता से PNG में बदलें\",\n    \"title\": \"JPG को PNG में बदलें\"\n  },\n  \"convertToJpg\": {\n    \"description\": \"अनुकूलन योग्य गुणवत्ता और पृष्ठभूमि रंग सेटिंग्स के साथ विभिन्न छवि प्रारूपों (PNG, GIF, TIF, PSD, SVG, WEBP, HEIC, RAW) को JPG में परिवर्तित करें।\",\n    \"shortDescription\": \"गुणवत्ता नियंत्रण के साथ छवियों को JPG में परिवर्तित करें\",\n    \"title\": \"छवियों को JPG में परिवर्तित करें\"\n  },\n  \"createTransparent\": {\n    \"description\": \"दुनिया\",\n    \"shortDescription\": \"किसी छवि को शीघ्रता से पारदर्शी बनाएँ\",\n    \"title\": \"पारदर्शी PNG बनाएँ\"\n  },\n  \"crop\": {\n    \"aspectRatio\": \"आकार अनुपात\",\n    \"cropArea\": \"क्रॉप क्षेत्र\",\n    \"cropMethod\": \"क्रॉप विधि\",\n    \"cropOptions\": \"क्रॉप विकल्प\",\n    \"description\": \"छवि से अनावश्यक भाग हटाएं।\",\n    \"height\": \"ऊंचाई\",\n    \"heightPlaceholder\": \"पिक्सेल\",\n    \"inputTitle\": \"इनपुट छवि\",\n    \"methodAspectRatio\": \"आकार अनुपात\",\n    \"methodManual\": \"मैनुअल\",\n    \"ratio16x9\": \"16:9\",\n    \"ratio1x1\": \"1:1\",\n    \"ratio3x2\": \"3:2\",\n    \"ratio4x3\": \"4:3\",\n    \"resultTitle\": \"क्रॉप की गई छवि\",\n    \"shortDescription\": \"छवियों को शीघ्रता से काटें.\",\n    \"title\": \"छवि क्रॉप करें\",\n    \"width\": \"चौड़ाई\",\n    \"widthPlaceholder\": \"पिक्सेल\",\n    \"xPlaceholder\": \"पिक्सेल\",\n    \"xPosition\": \"X स्थिति\",\n    \"yPlaceholder\": \"पिक्सेल\",\n    \"yPosition\": \"Y स्थिति\"\n  },\n  \"editor\": {\n    \"description\": \"क्रॉप करने, घुमाने, एनोटेट करने, रंग समायोजित करने और वॉटरमार्क जोड़ने के लिए उपकरणों के साथ उन्नत छवि संपादक। अपने ब्राउज़र में सीधे पेशेवर-स्तरीय उपकरणों से अपनी छवियों को संपादित करें।\",\n    \"shortDescription\": \"उन्नत टूल और सुविधाओं के साथ छवियों को संपादित करें\",\n    \"title\": \"छवि संपादक\"\n  },\n  \"filter\": {\n    \"description\": \"छवि पर विभिन्न फ़िल्टर लागू करें।\",\n    \"filterIntensity\": \"फ़िल्टर तीव्रता\",\n    \"filterOptions\": \"फ़िल्टर विकल्प\",\n    \"filterType\": \"फ़िल्टर प्रकार\",\n    \"inputTitle\": \"इनपुट छवि\",\n    \"intensityPlaceholder\": \"मान (0-100)\",\n    \"resultTitle\": \"फ़िल्टर की गई छवि\",\n    \"title\": \"छवि फ़िल्टर\",\n    \"typeBlur\": \"धुंधला\",\n    \"typeBrightness\": \"चमक\",\n    \"typeContrast\": \"कंट्रास्ट\",\n    \"typeGrayscale\": \"ग्रेस्केल\",\n    \"typeInvert\": \"उलटा\",\n    \"typeSaturation\": \"संतृप्ति\",\n    \"typeSepia\": \"सेपिया\",\n    \"typeSharpen\": \"तेज़\"\n  },\n  \"flip\": {\n    \"description\": \"छवि को क्षैतिज या लंबवत रूप से फ्लिप करें।\",\n    \"directionBoth\": \"दोनों\",\n    \"directionHorizontal\": \"क्षैतिज\",\n    \"directionVertical\": \"लंबवत\",\n    \"flipDirection\": \"फ्लिप दिशा\",\n    \"flipOptions\": \"फ्लिप विकल्प\",\n    \"inputTitle\": \"इनपुट छवि\",\n    \"resultTitle\": \"फ्लिप की गई छवि\",\n    \"title\": \"छवि फ्लिप करें\"\n  },\n  \"imageToText\": {\n    \"description\": \"ऑप्टिकल कैरेक्टर रिकॉग्निशन (OCR) का उपयोग करके छवियों (JPG, PNG) से पाठ निकालें।\",\n    \"shortDescription\": \"OCR का उपयोग करके छवियों से पाठ निकालें।\",\n    \"title\": \"छवि से पाठ (ओसीआर)\"\n  },\n  \"qrCode\": {\n    \"description\": \"विभिन्न डेटा प्रकारों के लिए क्यूआर कोड उत्पन्न करें: यूआरएल, टेक्स्ट, ईमेल, फोन, एसएमएस, वाईफाई, वीकार्ड, और अधिक।\",\n    \"shortDescription\": \"विभिन्न डेटा प्रारूपों के लिए अनुकूलित क्यूआर कोड बनाएं।\",\n    \"title\": \"क्यूआर कोड जनरेटर\"\n  },\n  \"removeBackground\": {\n    \"description\": \"दुनिया\",\n    \"shortDescription\": \"छवियों से पृष्ठभूमि स्वचालित रूप से हटाएँ\",\n    \"title\": \"छवि से पृष्ठभूमि हटाएँ\"\n  },\n  \"resize\": {\n    \"description\": \"छवि का आकार बदलें।\",\n    \"dimensionType\": \"आयाम प्रकार\",\n    \"height\": \"ऊंचाई\",\n    \"heightDescription\": \"ऊँचाई (पिक्सेल में)\",\n    \"heightPlaceholder\": \"मान\",\n    \"inputTitle\": \"इनपुट छवि\",\n    \"interpolationMethod\": \"इंटरपोलेशन विधि\",\n    \"maintainAspectRatio\": \"आकार अनुपात बनाए रखें\",\n    \"maintainAspectRatioDescription\": \"छवि का मूल पहलू अनुपात बनाए रखें.\",\n    \"methodAspectRatio\": \"आकार अनुपात\",\n    \"methodBicubic\": \"द्विघन\",\n    \"methodBilinear\": \"द्विरेखीय\",\n    \"methodNearest\": \"निकटतम\",\n    \"methodPercentage\": \"प्रतिशत\",\n    \"methodPixels\": \"पिक्सेल\",\n    \"percentage\": \"को PERCENTAGE\",\n    \"percentageDescription\": \"मूल आकार का प्रतिशत (उदाहरण के लिए, आधे आकार के लिए 50, दोहरे आकार के लिए 200)\",\n    \"resizeByPercentage\": \"प्रतिशत के अनुसार आकार बदलें\",\n    \"resizeByPercentageDescription\": \"मूल आकार का प्रतिशत निर्दिष्ट करके आकार बदलें।\",\n    \"resizeByPixels\": \"पिक्सेल द्वारा आकार बदलें\",\n    \"resizeByPixelsDescription\": \"पिक्सेल में आयाम निर्दिष्ट करके आकार बदलें.\",\n    \"resizeMethod\": \"आकार बदलने की विधि\",\n    \"resizeOptions\": \"आकार बदलने के विकल्प\",\n    \"resultTitle\": \"आकार बदली गई छवि\",\n    \"setHeight\": \"ऊँचाई निर्धारित करें\",\n    \"setHeightDescription\": \"पिक्सेल में ऊंचाई निर्दिष्ट करें और पहलू अनुपात के आधार पर चौड़ाई की गणना करें।\",\n    \"setWidth\": \"चौड़ाई सेट करें\",\n    \"setWidthDescription\": \"पिक्सेल में चौड़ाई निर्दिष्ट करें और पहलू अनुपात के आधार पर ऊंचाई की गणना करें।\",\n    \"shortDescription\": \"छवियों का आकार आसानी से बदलें.\",\n    \"title\": \"छवि आकार बदलें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको JPG, PNG, SVG, या GIF इमेज का आकार बदलने की सुविधा देता है। आप पिक्सेल या प्रतिशत में आयाम निर्दिष्ट करके आकार बदल सकते हैं, साथ ही मूल पहलू अनुपात बनाए रखने के विकल्प भी उपलब्ध हैं।\",\n      \"title\": \"छवि आकार बदलें\"\n    },\n    \"width\": \"चौड़ाई\",\n    \"widthDescription\": \"चौड़ाई (पिक्सेल में)\",\n    \"widthPlaceholder\": \"मान\"\n  },\n  \"rotate\": {\n    \"angle180\": \"180 डिग्री\",\n    \"angle270\": \"270 डिग्री\",\n    \"angle90\": \"90 डिग्री\",\n    \"backgroundColor\": \"पृष्ठभूमि रंग\",\n    \"colorBlack\": \"काला\",\n    \"colorCustom\": \"कस्टम\",\n    \"colorTransparent\": \"पारदर्शी\",\n    \"colorWhite\": \"सफेद\",\n    \"customAngle\": \"कस्टम कोण\",\n    \"customAnglePlaceholder\": \"डिग्री\",\n    \"customColorPlaceholder\": \"#RRGGBB\",\n    \"description\": \"छवि को निर्दिष्ट कोण से घुमाएं।\",\n    \"inputTitle\": \"इनपुट छवि\",\n    \"resultTitle\": \"घुमाई गई छवि\",\n    \"rotationAngle\": \"घुमाने का कोण\",\n    \"rotationOptions\": \"घुमाने के विकल्प\",\n    \"shortDescription\": \"किसी छवि को आसानी से घुमाएँ.\",\n    \"title\": \"छवि घुमाएं\"\n  },\n  \"watermark\": {\n    \"colorPlaceholder\": \"#RRGGBB\",\n    \"description\": \"छवि पर टेक्स्ट या छवि वॉटरमार्क जोड़ें।\",\n    \"fontColor\": \"फ़ॉन्ट रंग\",\n    \"fontSize\": \"फ़ॉन्ट आकार\",\n    \"fontSizePlaceholder\": \"आकार\",\n    \"imagePlaceholder\": \"छवि फ़ाइल\",\n    \"inputTitle\": \"इनपुट छवि\",\n    \"opacity\": \"पारदर्शिता\",\n    \"opacityPlaceholder\": \"प्रतिशत (1-100)\",\n    \"position\": \"स्थिति\",\n    \"positionBottomLeft\": \"नीचे बाएं\",\n    \"positionBottomRight\": \"नीचे दाएं\",\n    \"positionCenter\": \"केंद्र\",\n    \"positionTopLeft\": \"ऊपर बाएं\",\n    \"positionTopRight\": \"ऊपर दाएं\",\n    \"resultTitle\": \"वॉटरमार्क वाली छवि\",\n    \"textPlaceholder\": \"टेक्स्ट\",\n    \"title\": \"छवि पर वॉटरमार्क\",\n    \"typeImage\": \"छवि\",\n    \"typeText\": \"टेक्स्ट\",\n    \"watermarkImage\": \"वॉटरमार्क छवि\",\n    \"watermarkOptions\": \"वॉटरमार्क विकल्प\",\n    \"watermarkText\": \"वॉटरमार्क टेक्स्ट\",\n    \"watermarkType\": \"वॉटरमार्क प्रकार\"\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/json.json",
    "content": "{\n  \"comparison\": {\n    \"description\": \"दो JSON वस्तुओं की संरचना और मूल्यों में अंतर की पहचान करें।\",\n    \"shortDescription\": \"दो JSON वस्तुओं के बीच अंतर ढूंढें\",\n    \"title\": \"JSON तुलना करें\"\n  },\n  \"escape\": {\n    \"description\": \"JSON स्ट्रिंग में विशेष वर्णों को एस्केप करें।\",\n    \"escapeBackslashes\": \"बैकस्लैश एस्केप करें\",\n    \"escapeNewlines\": \"नई पंक्तियां एस्केप करें\",\n    \"escapeOptions\": \"एस्केप विकल्प\",\n    \"escapeQuotes\": \"उद्धरण एस्केप करें\",\n    \"escapeTabs\": \"टैब एस्केप करें\",\n    \"inputTitle\": \"इनपुट JSON\",\n    \"resultTitle\": \"एस्केप किया गया JSON\",\n    \"title\": \"JSON एस्केप करें\"\n  },\n  \"escapeJson\": {\n    \"description\": \"JSON स्ट्रिंग्स में विशेष वर्णों को एस्केप करें। सुरक्षित संचरण या संग्रहण के लिए JSON डेटा को उचित रूप से एस्केप किए गए प्रारूप में परिवर्तित करें।\",\n    \"shortDescription\": \"JSON में विशेष वर्णों से बचें\",\n    \"title\": \"JSON से बचें\"\n  },\n  \"jsonToXml\": {\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"description\": \"JSON डेटा को XML प्रारूप में बदलें।\",\n    \"includeAttributes\": \"विशेषताएं शामिल करें\",\n    \"indentSize\": \"इंडेंट आकार\",\n    \"inputTitle\": \"इनपुट JSON\",\n    \"resultTitle\": \"XML परिणाम\",\n    \"rootElement\": \"मूल तत्व\",\n    \"rootPlaceholder\": \"तत्व नाम\",\n    \"shortDescription\": \"JSON को XML प्रारूप में परिवर्तित करें\",\n    \"sizePlaceholder\": \"आकार\",\n    \"title\": \"JSON से XML\"\n  },\n  \"minify\": {\n    \"compactArrays\": \"सरणियां संक्षिप्त करें\",\n    \"description\": \"JSON को संक्षिप्त प्रारूप में बदलें।\",\n    \"inputTitle\": \"इनपुट JSON\",\n    \"minifyOptions\": \"संक्षिप्त करने के विकल्प\",\n    \"removeComments\": \"टिप्पणियां हटाएं\",\n    \"removeWhitespace\": \"सफेद स्थान हटाएं\",\n    \"resultTitle\": \"संक्षिप्त JSON\",\n    \"shortDescription\": \"रिक्त स्थान हटाकर JSON को छोटा करें\",\n    \"title\": \"JSON संक्षिप्त करें\",\n    \"toolInfo\": {\n      \"description\": \"JSON मिनिफिकेशन, JSON डेटा से सभी अनावश्यक रिक्त स्थान वर्णों को हटाने की प्रक्रिया है, जबकि इसकी वैधता बरकरार रहती है। इसमें रिक्त स्थान, नई पंक्तियाँ और इंडेंटेशन हटाना शामिल है जो JSON को सही ढंग से पार्स करने के लिए आवश्यक नहीं हैं। मिनिफिकेशन JSON डेटा के आकार को कम करता है, जिससे यह संग्रहण और संचरण के लिए अधिक कुशल हो जाता है, जबकि डेटा संरचना और मान बिल्कुल समान रहते हैं।\",\n      \"title\": \"JSON मिनिमाइजेशन क्या है?\"\n    }\n  },\n  \"prettify\": {\n    \"description\": \"JSON को सुंदर प्रारूप में बदलें।\",\n    \"formattingOptions\": \"फॉर्मेटिंग विकल्प\",\n    \"indentCharacter\": \"इंडेंट वर्ण\",\n    \"indentSize\": \"इंडेंट आकार\",\n    \"indentation\": \"खरोज\",\n    \"inputTitle\": \"इनपुट JSON\",\n    \"resultTitle\": \"सुंदर JSON\",\n    \"shortDescription\": \"JSON कोड को प्रारूपित और सुशोभित करें\",\n    \"sizePlaceholder\": \"आकार\",\n    \"sortKeys\": \"कुंजियां क्रमबद्ध करें\",\n    \"space\": \"स्पेस\",\n    \"tab\": \"टैब\",\n    \"title\": \"JSON सुंदर बनाएं\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको JSON डेटा को उचित इंडेंटेशन और स्पेसिंग के साथ फॉर्मेट करने की अनुमति देता है, जिससे यह अधिक पठनीय और काम करने में आसान हो जाता है।\",\n      \"title\": \"JSON सुंदर बनाएं\"\n    },\n    \"useSpaces\": \"रिक्त स्थान का उपयोग करें\",\n    \"useSpacesDescription\": \"रिक्त स्थान के साथ आउटपुट इंडेंट करें\",\n    \"useTabs\": \"टैब का उपयोग करें\",\n    \"useTabsDescription\": \"टैब के साथ आउटपुट इंडेंट करें.\"\n  },\n  \"stringify\": {\n    \"description\": \"JavaScript ऑब्जेक्ट को JSON स्ट्रिंग में बदलें।\",\n    \"includeFunctions\": \"फ़ंक्शन शामिल करें\",\n    \"includeUndefined\": \"अपरिभाषित शामिल करें\",\n    \"inputTitle\": \"इनपुट ऑब्जेक्ट\",\n    \"prettyPrint\": \"सुंदर प्रिंट\",\n    \"resultTitle\": \"JSON स्ट्रिंग\",\n    \"shortDescription\": \"ऑब्जेक्ट को JSON स्ट्रिंग में परिवर्तित करें\",\n    \"stringifyOptions\": \"स्ट्रिंगिफाई विकल्प\",\n    \"title\": \"JSON स्ट्रिंगिफाई\"\n  },\n  \"validateJson\": {\n    \"allowComments\": \"टिप्पणियां अनुमति दें\",\n    \"allowTrailingCommas\": \"अनुगामी कॉमा अनुमति दें\",\n    \"description\": \"JSON स्ट्रिंग की वैधता जांचें।\",\n    \"inputTitle\": \"इनपुट JSON\",\n    \"invalidJson\": \"❌ {{error}}\",\n    \"resultTitle\": \"मान्यता परिणाम\",\n    \"shortDescription\": \"त्रुटियों के लिए JSON कोड सत्यापित करें\",\n    \"strictMode\": \"सख्त मोड\",\n    \"title\": \"JSON मान्य करें\",\n    \"toolInfo\": {\n      \"description\": \"JSON (जावास्क्रिप्ट ऑब्जेक्ट नोटेशन) एक हल्का डेटा-इंटरचेंज फ़ॉर्मेट है। JSON सत्यापन यह सुनिश्चित करता है कि डेटा की संरचना JSON मानक के अनुरूप हो। एक मान्य JSON ऑब्जेक्ट में ये चीज़ें होनी चाहिए: - प्रॉपर्टी के नाम दोहरे उद्धरण चिह्नों में संलग्न हों। - उचित रूप से संतुलित कर्ली ब्रेसेज़ {}। - अंतिम कुंजी-मान युग्म के बाद कोई अनुगामी अल्पविराम न हो। - ऑब्जेक्ट्स और ऐरे का उचित नेस्टिंग। यह टूल इनपुट JSON की जाँच करता है और सामान्य त्रुटियों की पहचान करने और उन्हें ठीक करने में मदद के लिए फ़ीडबैक प्रदान करता है।\",\n      \"title\": \"JSON सत्यापन क्या है?\"\n    },\n    \"validJson\": \"✅ मान्य JSON\",\n    \"validationOptions\": \"मान्यता विकल्प\"\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/list.json",
    "content": "{\n  \"duplicate\": {\n    \"concatenate\": \"CONCATENATE\",\n    \"concatenateDescription\": \"प्रतियों को संयोजित करें (यदि अनचेक किया गया, तो आइटम आपस में गुंथ जाएंगे)\",\n    \"copyDescription\": \"प्रतियों की संख्या (आंशिक हो सकती है)\",\n    \"countPlaceholder\": \"संख्या\",\n    \"description\": \"सूची में आइटम को दोहराएं।\",\n    \"duplicateAll\": \"सभी आइटम दोहराएं\",\n    \"duplicateCount\": \"दोहराव की संख्या\",\n    \"duplicateEach\": \"प्रत्येक आइटम दोहराएं\",\n    \"duplicationOptions\": \"दोहराव विकल्प\",\n    \"error\": \"गलती\",\n    \"example1Description\": \"यह उदाहरण दिखाता है कि शब्दों की सूची की प्रतिलिपि कैसे बनाई जाए।\",\n    \"example1Title\": \"सरल दोहराव\",\n    \"example2Description\": \"यह उदाहरण दिखाता है कि किसी सूची को उल्टे क्रम में कैसे दोहराया जाए।\",\n    \"example2Title\": \"रिवर्स डुप्लिकेशन\",\n    \"example3Description\": \"यह उदाहरण दिखाता है कि वस्तुओं को संयोजित करने के बजाय उन्हें कैसे आपस में जोड़ा जाए।\",\n    \"example3Title\": \"आपस में जुड़ी हुई वस्तुएँ\",\n    \"example4Description\": \"यह उदाहरण दिखाता है कि किसी सूची की आंशिक संख्या की प्रतिलिपि कैसे बनाई जाए।\",\n    \"example4Title\": \"आंशिक दोहराव\",\n    \"examples\": {\n      \"fractional\": {\n        \"description\": \"यह उदाहरण दिखाता है कि किसी सूची की आंशिक संख्या की प्रतिलिपि कैसे बनाई जाए।\",\n        \"title\": \"आंशिक दोहराव\"\n      },\n      \"interweave\": {\n        \"description\": \"यह उदाहरण दिखाता है कि वस्तुओं को संयोजित करने के बजाय उन्हें कैसे आपस में जोड़ा जाए।\",\n        \"title\": \"आपस में जुड़ी हुई वस्तुएँ\"\n      },\n      \"reverse\": {\n        \"description\": \"यह उदाहरण दिखाता है कि किसी सूची को उल्टे क्रम में कैसे दोहराया जाए।\",\n        \"title\": \"रिवर्स डुप्लिकेशन\"\n      },\n      \"simple\": {\n        \"description\": \"यह उदाहरण दिखाता है कि शब्दों की सूची की प्रतिलिपि कैसे बनाई जाए।\",\n        \"title\": \"सरल दोहराव\"\n      }\n    },\n    \"inputTitle\": \"इनपुट सूची\",\n    \"joinSeparatorDescription\": \"डुप्लिकेट सूची में शामिल करने के लिए विभाजक\",\n    \"resultTitle\": \"दोहराई गई सूची\",\n    \"reverse\": \"सूची उलटा करें\",\n    \"reverseDescription\": \"डुप्लिकेट आइटम को उलट दें\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"निर्दिष्ट मानदंडों के साथ डुप्लिकेट सूची आइटम\",\n    \"splitByRegex\": \"नियमित अभिव्यक्ति द्वारा विभाजित\",\n    \"splitBySymbol\": \"प्रतीक द्वारा विभाजित\",\n    \"splitOptions\": \"विभाजित विकल्प\",\n    \"splitSeparatorDescription\": \"सूची को विभाजित करने के लिए विभाजक\",\n    \"title\": \"आइटम दोहराएं\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको सूची में आइटम्स की प्रतिलिपि बनाने की अनुमति देता है। आप प्रतियों की संख्या (आंशिक मानों सहित) निर्दिष्ट कर सकते हैं, नियंत्रित कर सकते हैं कि आइटम्स संयोजित हों या अंतर्गुंथित, और यहाँ तक कि डुप्लिकेट किए गए आइटम्स को उलट भी सकते हैं। यह दोहराए गए पैटर्न बनाने, परीक्षण डेटा उत्पन्न करने, या पूर्वानुमानित सामग्री वाली सूचियों का विस्तार करने के लिए उपयोगी है।\",\n      \"title\": \"सूची दोहराव\"\n    },\n    \"unknownError\": \"एक अज्ञात त्रुटि हुई\",\n    \"validation\": {\n      \"copyMustBeNumber\": \"प्रतियों की संख्या एक संख्या होनी चाहिए\",\n      \"copyMustBePositive\": \"प्रतियों की संख्या सकारात्मक होनी चाहिए\",\n      \"copyRequired\": \"प्रतियों की संख्या आवश्यक है\",\n      \"joinSeparatorRequired\": \"जॉइन सेपरेटर आवश्यक है\",\n      \"separatorRequired\": \"विभाजक आवश्यक है\"\n    }\n  },\n  \"findMostPopular\": {\n    \"caseSensitive\": \"केस संवेदी\",\n    \"countPlaceholder\": \"संख्या\",\n    \"description\": \"सूची में सबसे अधिक बार आने वाले आइटम खोजें।\",\n    \"displayFormatDescription\": \"सबसे लोकप्रिय सूची आइटम कैसे प्रदर्शित करें?\",\n    \"displayOptions\": {\n      \"count\": \"आइटम संख्या दिखाएँ\",\n      \"percentage\": \"आइटम प्रतिशत दिखाएँ\",\n      \"total\": \"कुल आइटम दिखाएँ\"\n    },\n    \"extractListItems\": \"सूची आइटम कैसे निकालें?\",\n    \"ignoreItemCase\": \"आइटम केस को अनदेखा करें\",\n    \"ignoreItemCaseDescription\": \"सभी सूची आइटमों की तुलना लोअरकेस में करें.\",\n    \"inputTitle\": \"इनपुट सूची\",\n    \"itemComparison\": \"आइटम तुलना\",\n    \"outputFormat\": \"शीर्ष आइटम आउटपुट प्रारूप\",\n    \"popularityOptions\": \"लोकप्रियता विकल्प\",\n    \"removeEmpty\": \"खाली आइटम हटाएं\",\n    \"removeEmptyItems\": \"खाली आइटम हटाएँ\",\n    \"removeEmptyItemsDescription\": \"तुलना में खाली आइटम को अनदेखा करें.\",\n    \"resultTitle\": \"लोकप्रिय आइटम\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"सबसे अधिक बार आने वाली वस्तुओं को खोजें\",\n    \"sortOptions\": {\n      \"alphabetic\": \"वर्णानुक्रम में क्रमबद्ध करें\",\n      \"count\": \"गिनती के अनुसार क्रमबद्ध करें\"\n    },\n    \"sortingMethodDescription\": \"एक सॉर्टिंग विधि का चयन करें.\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"नियमित अभिव्यक्ति के साथ इनपुट सूची आइटम को सीमांकित करें.\",\n        \"title\": \"विभाजन के लिए रेगेक्स का उपयोग करें\"\n      },\n      \"symbol\": {\n        \"description\": \"इनपुट सूची आइटम को किसी वर्ण से सीमांकित करें.\",\n        \"title\": \"विभाजन के लिए प्रतीक का उपयोग करें\"\n      }\n    },\n    \"splitSeparatorDescription\": \"एक सीमांकक प्रतीक या नियमित अभिव्यक्ति सेट करें.\",\n    \"title\": \"सबसे लोकप्रिय खोजें\",\n    \"topCount\": \"शीर्ष संख्या\",\n    \"trimItems\": \"शीर्ष सूची आइटम ट्रिम करें\",\n    \"trimItemsDescription\": \"आइटम की तुलना करने से पहले प्रारंभिक और अंतिम रिक्त स्थान हटा दें\"\n  },\n  \"findUnique\": {\n    \"caseSensitive\": \"केस संवेदी\",\n    \"caseSensitiveItems\": \"केस सेंसिटिव आइटम\",\n    \"caseSensitiveItemsDescription\": \"सूची में अलग-अलग केस वाले आइटम को अद्वितीय तत्व के रूप में आउटपुट करें।\",\n    \"delimiterDescription\": \"एक सीमांकक प्रतीक या नियमित अभिव्यक्ति सेट करें.\",\n    \"description\": \"सूची से अद्वितीय आइटम निकालें।\",\n    \"findAbsolutelyUniqueItems\": \"बिल्कुल अनोखी वस्तुएँ खोजें\",\n    \"findAbsolutelyUniqueItemsDescription\": \"सूची के केवल उन आइटमों को प्रदर्शित करें जो एकल प्रतिलिपि में मौजूद हों।\",\n    \"inputListDelimiter\": \"इनपुट सूची सीमांकक\",\n    \"inputTitle\": \"इनपुट सूची\",\n    \"outputListDelimiter\": \"आउटपुट सूची सीमांकक\",\n    \"removeEmpty\": \"खाली आइटम हटाएं\",\n    \"resultTitle\": \"अद्वितीय आइटम\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"सूची में अद्वितीय आइटम खोजें\",\n    \"skipEmptyItems\": \"खाली आइटम छोड़ें\",\n    \"skipEmptyItemsDescription\": \"आउटपुट में खाली सूची आइटम शामिल न करें।\",\n    \"title\": \"अद्वितीय आइटम खोजें\",\n    \"trimItems\": \"सूची आइटम ट्रिम करें\",\n    \"trimItemsDescription\": \"वस्तुओं की तुलना करने से पहले प्रारंभिक और अंतिम रिक्त स्थान हटा दें।\",\n    \"trimWhitespace\": \"सफेद स्थान ट्रिम करें\",\n    \"uniqueItemOptions\": \"अद्वितीय आइटम विकल्प\",\n    \"uniqueOptions\": \"अद्वितीय विकल्प\"\n  },\n  \"group\": {\n    \"deleteEmptyItems\": \"खाली आइटम हटाएं\",\n    \"deleteEmptyItemsDescription\": \"खाली आइटमों को अनदेखा करें और उन्हें समूहों में शामिल न करें।\",\n    \"description\": \"सूची आइटम को समूहित करने के लिए सरल उपकरण।\",\n    \"emptyItemsAndPadding\": \"खाली आइटम और पैडिंग\",\n    \"groupByFirstChar\": \"पहले वर्ण के अनुसार समूहित करें\",\n    \"groupByLastChar\": \"अंतिम वर्ण के अनुसार समूहित करें\",\n    \"groupByLength\": \"लंबाई के अनुसार समूहित करें\",\n    \"groupByPattern\": \"पैटर्न के अनुसार समूहित करें\",\n    \"groupHeaders\": \"समूह शीर्षक जोड़ें\",\n    \"groupNumberDescription\": \"एक समूह में वस्तुओं की संख्या\",\n    \"groupSeparatorDescription\": \"समूह विभाजक वर्ण\",\n    \"groupSizeAndSeparators\": \"समूह का आकार और विभाजक\",\n    \"groupingOptions\": \"समूहीकरण विकल्प\",\n    \"inputItemSeparator\": \"इनपुट आइटम विभाजक\",\n    \"inputTitle\": \"इनपुट सूची\",\n    \"itemSeparatorDescription\": \"आइटम विभाजक वर्ण\",\n    \"leftWrapDescription\": \"समूह का बायां आवरण प्रतीक.\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"padNonFullGroups\": \"पैड गैर-पूर्ण समूह\",\n    \"padNonFullGroupsDescription\": \"गैर-पूर्ण समूहों को कस्टम आइटम से भरें (नीचे दर्ज करें).\",\n    \"paddingCharDescription\": \"गैर-पूर्ण समूहों को पैड करने के लिए इस वर्ण या आइटम का उपयोग करें.\",\n    \"patternPlaceholder\": \"रेगेक्स पैटर्न\",\n    \"resultTitle\": \"समूहित सूची\",\n    \"rightWrapDescription\": \"समूह का दायाँ आवरण प्रतीक.\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"सामान्य गुणों द्वारा सूची आइटम को समूहित करें\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"नियमित अभिव्यक्ति के साथ इनपुट सूची आइटम को सीमांकित करें.\",\n        \"title\": \"विभाजन के लिए रेगेक्स का उपयोग करें\"\n      },\n      \"symbol\": {\n        \"description\": \"इनपुट सूची आइटम को किसी वर्ण से सीमांकित करें.\",\n        \"title\": \"विभाजन के लिए प्रतीक का उपयोग करें\"\n      }\n    },\n    \"splitSeparatorDescription\": \"एक सीमांकक प्रतीक या नियमित अभिव्यक्ति सेट करें.\",\n    \"title\": \"सूची समूहित करें\"\n  },\n  \"reverse\": {\n    \"description\": \"सूची में आइटम के क्रम को उलटा करें।\",\n    \"inputTitle\": \"इनपुट सूची\",\n    \"itemSeparator\": \"आइटम विभाजक\",\n    \"itemSeparatorDescription\": \"एक सीमांकक प्रतीक या नियमित अभिव्यक्ति सेट करें.\",\n    \"outputListOptions\": \"आउटपुट सूची विकल्प\",\n    \"outputSeparatorDescription\": \"आउटपुट सूची आइटम विभाजक.\",\n    \"resultTitle\": \"उलटी सूची\",\n    \"reverseEachItem\": \"प्रत्येक आइटम उलटा करें\",\n    \"reverseOptions\": \"उलटाने के विकल्प\",\n    \"reverseOrder\": \"क्रम उलटा करें\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"जल्दी से सूची को उलटा करें\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"नियमित अभिव्यक्ति के साथ इनपुट सूची आइटम को सीमांकित करें.\",\n        \"title\": \"विभाजन के लिए रेगेक्स का उपयोग करें\"\n      },\n      \"symbol\": {\n        \"description\": \"इनपुट सूची आइटम को किसी वर्ण से सीमांकित करें.\",\n        \"title\": \"विभाजन के लिए प्रतीक का उपयोग करें\"\n      }\n    },\n    \"splitterMode\": \"स्प्लिटर मोड\",\n    \"title\": \"सूची उलटा करें\",\n    \"toolInfo\": {\n      \"description\": \"इस उपयोगिता से, आप किसी सूची में आइटम्स के क्रम को उलट सकते हैं। यह उपयोगिता पहले इनपुट सूची को अलग-अलग आइटम्स में विभाजित करती है और फिर अंतिम आइटम से पहले आइटम तक उनमें पुनरावृति करती है, और पुनरावृत्ति के दौरान प्रत्येक आइटम को आउटपुट में प्रिंट करती है। इनपुट सूची में कुछ भी हो सकता है जिसे पाठ्य डेटा के रूप में दर्शाया जा सकता है, जिसमें अंक, संख्याएँ, स्ट्रिंग, शब्द, वाक्य आदि शामिल हैं। इनपुट आइटम विभाजक एक नियमित अभिव्यक्ति भी हो सकता है। उदाहरण के लिए, रेगुलर एक्सप्रेशन /[;,]/ आपको अल्पविराम या अर्धविराम से अलग किए गए आइटम्स का उपयोग करने की अनुमति देगा। इनपुट और आउटपुट सूची आइटम विभाजक विकल्पों में अनुकूलित किए जा सकते हैं। डिफ़ॉल्ट रूप से, इनपुट और आउटपुट दोनों सूचियाँ अल्पविराम से अलग होती हैं। Listabulous!\",\n      \"title\": \"सूची उलटने वाला क्या है?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"सूची में आइटम को निर्दिष्ट स्थानों द्वारा घुमाएं।\",\n    \"inputTitle\": \"इनपुट सूची\",\n    \"resultTitle\": \"घुमाई गई सूची\",\n    \"rotateDirection\": \"घुमाने की दिशा\",\n    \"rotateLeft\": \"बाईं ओर घुमाएं\",\n    \"rotateRight\": \"दाईं ओर घुमाएं\",\n    \"rotateSteps\": \"घुमाने के चरण\",\n    \"rotationOptions\": \"घुमाने के विकल्प\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"सूची आइटम को निर्दिष्ट स्थानों पर घुमाएँ\",\n    \"stepsPlaceholder\": \"चरणों की संख्या\",\n    \"title\": \"सूची घुमाएं\"\n  },\n  \"shuffle\": {\n    \"delimiterDescription\": \"एक सीमांकक प्रतीक या नियमित अभिव्यक्ति सेट करें.\",\n    \"description\": \"सूची में आइटम को यादृच्छिक क्रम में व्यवस्थित करें।\",\n    \"inputListSeparator\": \"इनपुट सूची विभाजक\",\n    \"inputTitle\": \"इनपुट सूची\",\n    \"joinSeparatorDescription\": \"यादृच्छिक सूची में इस विभाजक का उपयोग करें।\",\n    \"outputLengthDescription\": \"इतने सारे यादृच्छिक आइटम आउटपुट करें\",\n    \"randomSeed\": \"यादृच्छिक बीज\",\n    \"resultTitle\": \"फेरबदल की गई सूची\",\n    \"seedPlaceholder\": \"बीज मान\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"सूची आइटमों का क्रम यादृच्छिक करें\",\n    \"shuffleOptions\": \"फेरबदल विकल्प\",\n    \"shuffledListLength\": \"फेरबदल सूची की लंबाई\",\n    \"shuffledListSeparator\": \"फेरबदल सूची विभाजक\",\n    \"title\": \"सूची फेरबदल करें\"\n  },\n  \"sort\": {\n    \"ascending\": \"आरोही\",\n    \"caseSensitive\": \"केस सेंसिटिव सॉर्ट\",\n    \"caseSensitiveDescription\": \"बड़े और छोटे अक्षरों वाले आइटम को अलग-अलग क्रमबद्ध करें। बड़े अक्षरों को आरोही क्रम में छोटे अक्षरों से पहले रखें। (केवल वर्णानुक्रमिक क्रम में काम करता है।)\",\n    \"descending\": \"अवरोही\",\n    \"description\": \"सूची में आइटम को क्रमबद्ध करें।\",\n    \"inputItemSeparator\": \"इनपुट आइटम विभाजक\",\n    \"inputTitle\": \"इनपुट सूची\",\n    \"joinSeparatorDescription\": \"इस प्रतीक का उपयोग क्रमबद्ध सूची में आइटमों के बीच जोड़ने वाले के रूप में करें।\",\n    \"orderDescription\": \"एक सॉर्टिंग क्रम का चयन करें.\",\n    \"orderOptions\": {\n      \"decreasing\": \"घटते क्रम\",\n      \"increasing\": \"बढ़ते क्रम\"\n    },\n    \"removeDuplicates\": \"डुप्लिकेट हटाएं\",\n    \"removeDuplicatesDescription\": \"डुप्लिकेट सूची आइटम हटाएं.\",\n    \"removeEmpty\": \"खाली आइटम हटाएं\",\n    \"resultTitle\": \"क्रमबद्ध सूची\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"सूची आइटम को निर्दिष्ट क्रम में सॉर्ट करें\",\n    \"sortAlphabetically\": \"वर्णानुक्रमिक रूप से क्रमबद्ध करें\",\n    \"sortByLength\": \"लंबाई के अनुसार क्रमबद्ध करें\",\n    \"sortMethod\": \"सॉर्ट विधि\",\n    \"sortMethodDescription\": \"एक सॉर्टिंग विधि का चयन करें.\",\n    \"sortNumerically\": \"संख्यात्मक रूप से क्रमबद्ध करें\",\n    \"sortOptions\": {\n      \"alphabetic\": \"वर्णानुक्रम में क्रमबद्ध करें\",\n      \"length\": \"लंबाई के अनुसार क्रमबद्ध करें\",\n      \"numeric\": \"संख्यात्मक रूप से क्रमबद्ध करें\"\n    },\n    \"sortOrder\": \"क्रमबद्ध करने का क्रम\",\n    \"sortedItemProperties\": \"क्रमबद्ध आइटम गुण\",\n    \"sortingOptions\": \"क्रमबद्ध करने के विकल्प\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"नियमित अभिव्यक्ति के साथ इनपुट सूची आइटम को सीमांकित करें.\",\n        \"title\": \"विभाजन के लिए रेगेक्स का उपयोग करें\"\n      },\n      \"symbol\": {\n        \"description\": \"इनपुट सूची आइटम को किसी वर्ण से सीमांकित करें.\",\n        \"title\": \"विभाजन के लिए प्रतीक का उपयोग करें\"\n      }\n    },\n    \"splitSeparatorDescription\": \"एक सीमांकक प्रतीक या नियमित अभिव्यक्ति सेट करें.\",\n    \"title\": \"सूची क्रमबद्ध करें\",\n    \"trimWhitespace\": \"सफेद स्थान ट्रिम करें\"\n  },\n  \"truncate\": {\n    \"description\": \"सूची को निर्दिष्ट लंबाई तक काटें।\",\n    \"inputTitle\": \"इनपुट सूची\",\n    \"lengthPlaceholder\": \"लंबाई\",\n    \"resultTitle\": \"काटी गई सूची\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"सूची को निर्दिष्ट संख्या में आइटम तक छोटा करें\",\n    \"title\": \"सूची काटें\",\n    \"truncateFrom\": \"कहाँ से काटें\",\n    \"truncateFromEnd\": \"अंत से\",\n    \"truncateFromStart\": \"शुरुआत से\",\n    \"truncateLength\": \"काटने की लंबाई\",\n    \"truncationOptions\": \"काटने के विकल्प\"\n  },\n  \"unwrap\": {\n    \"characterPlaceholder\": \"वर्ण\",\n    \"description\": \"लपेटी गई सूची को खोलें।\",\n    \"inputTitle\": \"इनपुट सूची\",\n    \"removeEmpty\": \"खाली आइटम हटाएं\",\n    \"resultTitle\": \"खोली गई सूची\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"संरचित प्रारूप से सूची आइटम खोलना\",\n    \"title\": \"सूची खोलें\",\n    \"unwrapCharacter\": \"खोलने का वर्ण\",\n    \"unwrapOptions\": \"खोलने के विकल्प\"\n  },\n  \"wrap\": {\n    \"characterPlaceholder\": \"वर्ण\",\n    \"description\": \"सूची आइटम को निर्दिष्ट लंबाई में लपेटें।\",\n    \"inputTitle\": \"इनपुट सूची\",\n    \"joinSeparatorDescription\": \"लिपटे सूची में शामिल करने के लिए विभाजक\",\n    \"leftTextDescription\": \"प्रत्येक आइटम से पहले जोड़ने के लिए पाठ\",\n    \"removeEmptyItems\": \"खाली आइटम हटाएँ\",\n    \"resultTitle\": \"लपेटी गई सूची\",\n    \"rightTextDescription\": \"प्रत्येक आइटम के बाद जोड़ने के लिए पाठ\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"सूची आइटम को निर्दिष्ट मानदंडों के साथ लपेटें\",\n    \"splitByRegex\": \"नियमित अभिव्यक्ति द्वारा विभाजित\",\n    \"splitBySymbol\": \"प्रतीक द्वारा विभाजित\",\n    \"splitOptions\": \"विभाजित विकल्प\",\n    \"splitSeparatorDescription\": \"सूची को विभाजित करने के लिए विभाजक\",\n    \"title\": \"सूची लपेटें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको सूची में प्रत्येक आइटम के पहले और बाद में टेक्स्ट जोड़ने की अनुमति देता है। आप बाएँ और दाएँ पक्षों के लिए अलग-अलग टेक्स्ट निर्दिष्ट कर सकते हैं, और सूची को कैसे संसाधित किया जाए, इसे नियंत्रित कर सकते हैं। यह सूची आइटम में उद्धरण चिह्न, कोष्ठक या अन्य स्वरूपण जोड़ने, विभिन्न स्वरूपों के लिए डेटा तैयार करने, या संरचित टेक्स्ट बनाने के लिए उपयोगी है।\",\n      \"title\": \"सूची लपेटना\"\n    },\n    \"widthPlaceholder\": \"चौड़ाई\",\n    \"wrapCharacter\": \"लपेटने का वर्ण\",\n    \"wrapOptions\": \"लपेटने के विकल्प\",\n    \"wrapWidth\": \"लपेटने की चौड़ाई\"\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/number.json",
    "content": "{\n  \"arithmeticSequence\": {\n    \"commonDifference\": \"सामान्य अंतर\",\n    \"commonDifferenceDescription\": \"शब्दों के बीच सामान्य अंतर (d)\",\n    \"description\": \"अंकगणितीय अनुक्रम उत्पन्न करें।\",\n    \"differencePlaceholder\": \"अंतर\",\n    \"firstTerm\": \"पहला पद\",\n    \"firstTermDescription\": \"अनुक्रम का पहला पद (a₁)\",\n    \"firstTermPlaceholder\": \"पद\",\n    \"inputTitle\": \"अनुक्रम\",\n    \"numberOfTerms\": \"पदों की संख्या\",\n    \"numberOfTermsDescription\": \"उत्पन्न करने के लिए पदों की संख्या (n)\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"resultTitle\": \"अंकगणितीय अनुक्रम\",\n    \"separatorDescription\": \"शब्दों के बीच विभाजक\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"sequenceOptions\": \"अनुक्रम विकल्प\",\n    \"sequenceParameters\": \"अनुक्रम पैरामीटर\",\n    \"shortDescription\": \"अंकगणितीय अनुक्रम उत्पन्न करें\",\n    \"termsPlaceholder\": \"संख्या\",\n    \"title\": \"अंकगणितीय अनुक्रम\",\n    \"toolInfo\": {\n      \"description\": \"अंकगणितीय अनुक्रम संख्याओं का एक ऐसा अनुक्रम होता है जहाँ प्रत्येक क्रमागत पद का अंतर स्थिर होता है। इस स्थिर अंतर को सार्व अंतर कहते हैं। पहला पद (a₁) और सार्व अंतर (d) दिए होने पर, प्रत्येक पद को पिछले पद में सार्व अंतर जोड़कर ज्ञात किया जा सकता है।\",\n      \"title\": \"अंकगणितीय अनुक्रम क्या है?\"\n    }\n  },\n  \"generate\": {\n    \"arithmeticSequenceOption\": \"अंकगणितीय अनुक्रम विकल्प\",\n    \"countNumbers\": \"संख्याओं की संख्या\",\n    \"countPlaceholder\": \"संख्या\",\n    \"description\": \"निर्दिष्ट मापदंडों के अनुसार संख्याओं की सूची उत्पन्न करें।\",\n    \"generationOptions\": \"उत्पन्न करने के विकल्प\",\n    \"inputTitle\": \"उत्पन्न की गई संख्याएं\",\n    \"numberOfElementsDescription\": \"अनुक्रम में तत्वों की संख्या.\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"resultTitle\": \"संख्याओं की सूची\",\n    \"separator\": \"सेपरेटर\",\n    \"separatorDescription\": \"इस वर्ण द्वारा अंकगणितीय अनुक्रम में तत्वों को अलग करें।\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"निर्दिष्ट श्रेणियों में यादृच्छिक संख्याएँ उत्पन्न करें\",\n    \"startNumber\": \"शुरुआती संख्या\",\n    \"startPlaceholder\": \"संख्या\",\n    \"startSequenceDescription\": \"इस संख्या से अनुक्रम प्रारंभ करें.\",\n    \"stepDescription\": \"प्रत्येक तत्व को इस मात्रा से बढ़ाएँ\",\n    \"stepPlaceholder\": \"मान\",\n    \"stepValue\": \"चरण मान\",\n    \"title\": \"संख्याएं उत्पन्न करें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको अनुकूलन योग्य मापदंडों के साथ संख्याओं का एक क्रम बनाने की अनुमति देता है। आप प्रारंभिक मान, चरण आकार और तत्वों की संख्या निर्दिष्ट कर सकते हैं।\",\n      \"title\": \"संख्याएं उत्पन्न करें\"\n    }\n  },\n  \"ohmsLaw\": {\n    \"description\": \"वोल्टेज, धारा और प्रतिरोध की गणना करता है\",\n    \"longDescription\": \"यह कैलकुलेटर ओम के नियम (V = I × R) का उपयोग करके तीन विद्युत प्राचलों में से किसी एक का निर्धारण करता है, जब अन्य दो ज्ञात हों। ओम का नियम विद्युत इंजीनियरिंग का एक मूलभूत सिद्धांत है जो वोल्टेज (V), धारा (I), और प्रतिरोध (R) के बीच संबंध का वर्णन करता है। यह उपकरण इलेक्ट्रॉनिक्स के शौकीनों, विद्युत इंजीनियरों और सर्किट पर काम करने वाले छात्रों के लिए उनके विद्युत डिज़ाइनों में अज्ञात मानों को शीघ्रता से हल करने हेतु आवश्यक है।\",\n    \"shortDescription\": \"ओम के नियम का उपयोग करके विद्युत परिपथों में वोल्टेज, धारा या प्रतिरोध की गणना करें\",\n    \"title\": \"ओम कानून\"\n  },\n  \"randomNumberGenerator\": {\n    \"description\": \"अनुकूलन योग्य विकल्पों के साथ निर्दिष्ट सीमा के भीतर यादृच्छिक संख्याएँ उत्पन्न करें।\",\n    \"error\": {\n      \"generationFailed\": \"यादृच्छिक संख्याएँ जनरेट करने में विफल. कृपया अपने इनपुट पैरामीटर जाँचें.\"\n    },\n    \"info\": {\n      \"description\": \"एक यादृच्छिक संख्या जनरेटर एक निर्दिष्ट सीमा के भीतर अप्रत्याशित संख्याएँ उत्पन्न करता है। यह उपकरण वास्तव में यादृच्छिक परिणाम सुनिश्चित करने के लिए क्रिप्टोग्राफ़िक रूप से सुरक्षित यादृच्छिक संख्या जनन का उपयोग करता है। सिमुलेशन, खेल, सांख्यिकीय नमूनाकरण और परीक्षण परिदृश्यों के लिए उपयोगी।\",\n      \"title\": \"रैंडम नंबर जनरेटर क्या है?\"\n    },\n    \"longDescription\": \"पूर्णांकों या दशमलवों के विकल्पों के साथ एक निर्दिष्ट सीमा के भीतर यादृच्छिक संख्याएँ उत्पन्न करें, डुप्लिकेट की अनुमति दें या रोकें, और परिणामों को क्रमबद्ध करें। सिमुलेशन, परीक्षण, खेल और सांख्यिकीय विश्लेषण के लिए बिल्कुल उपयुक्त।\",\n    \"options\": {\n      \"generation\": {\n        \"allowDecimals\": {\n          \"description\": \"पूर्णांकों के बजाय दशमलव संख्याएँ उत्पन्न करें\",\n          \"title\": \"दशमलव संख्याओं की अनुमति दें\"\n        },\n        \"allowDuplicates\": {\n          \"description\": \"एक ही नंबर को कई बार प्रदर्शित होने दें\",\n          \"title\": \"डुप्लिकेट की अनुमति दें\"\n        },\n        \"countDescription\": \"उत्पन्न की जाने वाली यादृच्छिक संख्याओं की संख्या (1-10,000)\",\n        \"sortResults\": {\n          \"description\": \"उत्पन्न संख्याओं को आरोही क्रम में क्रमबद्ध करें\",\n          \"title\": \"परिणाम क्रमबद्ध करें\"\n        },\n        \"title\": \"पीढ़ी के विकल्प\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"उत्पन्न संख्याओं को अलग करने के लिए वर्ण\",\n        \"title\": \"उत्पादन का वातावरण\"\n      },\n      \"range\": {\n        \"maxDescription\": \"अधिकतम मूल्य (समावेशी)\",\n        \"minDescription\": \"न्यूनतम मूल्य (समावेशी)\",\n        \"title\": \"रेंज सेटिंग्स\"\n      }\n    },\n    \"result\": {\n      \"count\": \"गिनती करना\",\n      \"hasDuplicates\": \"डुप्लिकेट शामिल हैं\",\n      \"isSorted\": \"क्रमबद्ध\",\n      \"range\": \"श्रेणी\",\n      \"title\": \"उत्पन्न यादृच्छिक संख्याएँ\"\n    },\n    \"shortDescription\": \"कस्टम श्रेणियों में यादृच्छिक संख्याएँ उत्पन्न करें\",\n    \"title\": \"रैंडम संख्या जनरेटर\"\n  },\n  \"randomPortGenerator\": {\n    \"description\": \"अनुकूलन योग्य विकल्पों के साथ निर्दिष्ट सीमाओं के भीतर यादृच्छिक नेटवर्क पोर्ट उत्पन्न करें।\",\n    \"error\": {\n      \"generationFailed\": \"यादृच्छिक पोर्ट जनरेट करने में विफल. कृपया अपने इनपुट पैरामीटर जांचें.\"\n    },\n    \"info\": {\n      \"description\": \"एक रैंडम पोर्ट जनरेटर निर्दिष्ट सीमाओं के भीतर अप्रत्याशित नेटवर्क पोर्ट संख्याएँ बनाता है। यह उपकरण IANA पोर्ट संख्या मानकों का पालन करता है और इसमें सामान्य सेवाओं की पहचान शामिल है। विकास, परीक्षण, नेटवर्क कॉन्फ़िगरेशन और पोर्ट टकराव से बचने के लिए उपयोगी।\",\n      \"title\": \"रैंडम पोर्ट जनरेटर क्या है?\"\n    },\n    \"longDescription\": \"निर्दिष्ट श्रेणियों (प्रसिद्ध, पंजीकृत, गतिशील, या कस्टम) के भीतर यादृच्छिक नेटवर्क पोर्ट उत्पन्न करें। विकास, परीक्षण और नेटवर्क कॉन्फ़िगरेशन के लिए उपयुक्त। सामान्य पोर्ट के लिए पोर्ट सेवा पहचान शामिल है।\",\n    \"options\": {\n      \"generation\": {\n        \"allowDuplicates\": {\n          \"description\": \"एक ही पोर्ट को कई बार प्रदर्शित होने दें\",\n          \"title\": \"डुप्लिकेट की अनुमति दें\"\n        },\n        \"countDescription\": \"उत्पन्न किए जाने वाले यादृच्छिक पोर्ट की संख्या (1-1,000)\",\n        \"sortResults\": {\n          \"description\": \"उत्पन्न पोर्ट को आरोही क्रम में क्रमबद्ध करें\",\n          \"title\": \"परिणाम क्रमबद्ध करें\"\n        },\n        \"title\": \"पीढ़ी के विकल्प\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"उत्पन्न पोर्ट को अलग करने के लिए वर्ण\",\n        \"title\": \"उत्पादन का वातावरण\"\n      },\n      \"range\": {\n        \"custom\": \"कस्टम रेंज\",\n        \"dynamic\": \"डायनेमिक पोर्ट्स (49152-65535)\",\n        \"maxPortDescription\": \"अधिकतम पोर्ट संख्या (1-65535)\",\n        \"minPortDescription\": \"न्यूनतम पोर्ट संख्या (1-65535)\",\n        \"registered\": \"पंजीकृत बंदरगाह (1024-49151)\",\n        \"title\": \"पोर्ट रेंज सेटिंग्स\",\n        \"wellKnown\": \"प्रसिद्ध बंदरगाह (1-1023)\"\n      }\n    },\n    \"result\": {\n      \"count\": \"गिनती करना\",\n      \"hasDuplicates\": \"डुप्लिकेट शामिल हैं\",\n      \"isSorted\": \"क्रमबद्ध\",\n      \"portDetails\": \"बंदरगाह विवरण\",\n      \"range\": \"पोर्ट रेंज\",\n      \"title\": \"उत्पन्न यादृच्छिक पोर्ट\"\n    },\n    \"shortDescription\": \"यादृच्छिक नेटवर्क पोर्ट उत्पन्न करें\",\n    \"title\": \"रैंडम पोर्ट जनरेटर\"\n  },\n  \"slackline\": {\n    \"description\": \"स्लैकलाइन में तनाव की गणना करता है\",\n    \"longDescription\": \"यह कैलकुलेटर रस्सी के केंद्र में भार मानता है\",\n    \"shortDescription\": \"स्लैकलाइन या क्लोथलाइन के अनुमानित तनाव की गणना करें। सुरक्षा के लिए इस पर निर्भर न रहें।\",\n    \"title\": \"स्लैकलाइन तनाव\"\n  },\n  \"sphereArea\": {\n    \"description\": \"गोले का क्षेत्रफल\",\n    \"longDescription\": \"यह कैलकुलेटर सूत्र A = 4πr² का उपयोग करके एक गोले का पृष्ठीय क्षेत्रफल ज्ञात करता है। आप पृष्ठीय क्षेत्रफल ज्ञात करने के लिए त्रिज्या दर्ज कर सकते हैं या आवश्यक त्रिज्या की गणना करने के लिए पृष्ठीय क्षेत्रफल दर्ज कर सकते हैं। यह उपकरण ज्यामिति के छात्रों, गोलाकार वस्तुओं पर काम करने वाले इंजीनियरों, और गोलाकार सतहों से संबंधित गणनाएँ करने वाले किसी भी व्यक्ति के लिए उपयोगी है।\",\n    \"shortDescription\": \"एक गोले के सतह क्षेत्र की गणना उसकी त्रिज्या के आधार पर करें\",\n    \"title\": \"गोले का क्षेत्रफल\"\n  },\n  \"sphereVolume\": {\n    \"description\": \"गोले का आयतन\",\n    \"longDescription\": \"यह कैलकुलेटर सूत्र V = (4/3)πr³ का उपयोग करके एक गोले का आयतन ज्ञात करता है। आप आयतन ज्ञात करने के लिए त्रिज्या या व्यास दर्ज कर सकते हैं, या आवश्यक त्रिज्या ज्ञात करने के लिए आयतन दर्ज कर सकते हैं। यह उपकरण भौतिकी, इंजीनियरिंग और विनिर्माण जैसे क्षेत्रों में गोलाकार वस्तुओं के साथ काम करने वाले छात्रों, इंजीनियरों और पेशेवरों के लिए उपयोगी है।\",\n    \"shortDescription\": \"त्रिज्या या व्यास का उपयोग करके एक गोले का आयतन ज्ञात करें\",\n    \"title\": \"गोले का आयतन\"\n  },\n  \"sum\": {\n    \"description\": \"संख्याओं की सूची का योग करें।\",\n    \"example1Description\": \"इस उदाहरण में, हम दस धनात्मक पूर्णांकों का योगफल ज्ञात करते हैं। ये पूर्णांक एक स्तंभ में सूचीबद्ध हैं और इनका कुल योग 19494 के बराबर है।\",\n    \"example1Title\": \"दस धनात्मक संख्याओं का योग\",\n    \"example2Description\": \"यह उदाहरण तेईस-अक्षर वाले संज्ञाओं के एक कॉलम को उलट देता है और सभी शब्दों को नीचे से ऊपर तक प्रिंट करता है। सूची आइटम को अलग करने के लिए, यह \\\\n वर्ण को इनपुट आइटम विभाजक के रूप में उपयोग करता है, जिसका अर्थ है कि प्रत्येक आइटम अपनी अलग पंक्ति में है।\",\n    \"example2Title\": \"पार्क में पेड़ों की गिनती करें\",\n    \"example3Description\": \"इस उदाहरण में, हम नब्बे अलग-अलग मानों को जोड़ते हैं – धनात्मक संख्याएँ, ऋणात्मक संख्याएँ, पूर्णांक और दशमलव भिन्न। हम इनपुट विभाजक को अल्पविराम पर सेट करते हैं और उन सभी को जोड़ने के बाद, हमें आउटपुट के रूप में 0 प्राप्त होता है।\",\n    \"example3Title\": \"पूर्णांकों और दशमलवों का योग\",\n    \"example4Description\": \"इस उदाहरण में, हम सभी दस अंकों का योगफल निकालते हैं और \\\"प्रिंट रनिंग योग\\\" विकल्प को सक्षम करते हैं। योग की प्रक्रिया में हमें योगफल के मध्यवर्ती मान प्राप्त होते हैं। इस प्रकार, हमें आउटपुट में निम्नलिखित क्रम प्राप्त होता है: 0, 1 (0 + 1), 3 (0 + 1 + 2), 6 (0 + 1 + 2 + 3), 10 (0 + 1 + 2 + 3 + 4), इत्यादि।\",\n    \"example4Title\": \"संख्याओं का चलित योग\",\n    \"extractionTypes\": {\n      \"delimiter\": {\n        \"description\": \"यहां संख्या विभाजक को अनुकूलित करें। (डिफ़ॉल्ट रूप से एक लाइन ब्रेक।)\",\n        \"title\": \"संख्या सीमांकक\"\n      },\n      \"smart\": {\n        \"description\": \"इनपुट में संख्याओं का स्वतः पता लगाना.\",\n        \"title\": \"स्मार्ट योग\"\n      }\n    },\n    \"ignoreNonNumbers\": \"गैर-संख्यात्मक मान अनदेखा करें\",\n    \"inputTitle\": \"इनपुट संख्याएं\",\n    \"numberExtraction\": \"संख्या निष्कर्षण\",\n    \"printRunningSum\": \"चल योग प्रिंट करें\",\n    \"printRunningSumDescription\": \"चरण दर चरण गणना करके योग प्रदर्शित करें।\",\n    \"resultFormat\": \"परिणाम प्रारूप\",\n    \"resultTitle\": \"योग\",\n    \"runningSum\": \"चल योग\",\n    \"separatorPlaceholder\": \"विभाजक\",\n    \"shortDescription\": \"संख्याओं का योग ज्ञात कीजिए\",\n    \"showAverage\": \"औसत दिखाएं\",\n    \"showCount\": \"गणना दिखाएं\",\n    \"showSum\": \"योग दिखाएं\",\n    \"sumOptions\": \"जोड़ने के विकल्प\",\n    \"title\": \"संख्याएं जोड़ें\",\n    \"toolInfo\": {\n      \"description\": \"यह संख्याओं के समूह का योग निकालने के लिए एक ऑनलाइन ब्राउज़र-आधारित उपयोगिता है। आप संख्याओं को अल्पविराम, रिक्त स्थान या किसी अन्य वर्ण (लाइन ब्रेक सहित) से अलग करके दर्ज कर सकते हैं। आप पाठ्य डेटा का एक अंश भी पेस्ट कर सकते हैं जिसमें वे संख्यात्मक मान हों जिनका आप योग करना चाहते हैं और उपयोगिता उन्हें निकालकर उनका योग ज्ञात कर लेगी।\",\n      \"title\": \"संख्या योग कैलकुलेटर क्या है?\"\n    }\n  },\n  \"voltageDropInWire\": {\n    \"description\": \"2 कंडक्टर केबल में राउंड ट्रिप वोल्टेज और बिजली हानि की गणना करता है\",\n    \"longDescription\": \"यह कैलकुलेटर दो-चालक विद्युत केबल में वोल्टेज ड्रॉप और पावर लॉस का पता लगाने में मदद करता है। यह केबल की लंबाई, तार के गेज (अनुप्रस्थ क्षेत्र), पदार्थ की प्रतिरोधकता और धारा प्रवाह को ध्यान में रखता है। यह उपकरण राउंड-ट्रिप वोल्टेज ड्रॉप, केबल के कुल प्रतिरोध और ऊष्मा के रूप में क्षय होने वाली शक्ति की गणना करता है। यह विशेष रूप से विद्युत इंजीनियरों, इलेक्ट्रीशियनों और शौकिया लोगों के लिए उपयोगी है, जब वे विद्युत प्रणालियों को डिज़ाइन करते हैं ताकि यह सुनिश्चित किया जा सके कि वोल्टेज का स्तर लोड पर स्वीकार्य सीमा के भीतर रहे।\",\n    \"shortDescription\": \"लंबाई, सामग्री और धारा के आधार पर विद्युत केबलों में वोल्टेज ड्रॉप और बिजली हानि की गणना करें\",\n    \"title\": \"केबल में राउंड ट्रिप वोल्टेज ड्रॉप\"\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/pdf.json",
    "content": "{\n  \"compressPdf\": {\n    \"compressedFileSize\": \"संपीड़ित फ़ाइल का आकार\",\n    \"compressingPdf\": \"पीडीएफ संपीड़ित किया जा रहा है...\",\n    \"compressionLevel\": \"संपीड़न स्तर\",\n    \"compressionOptions\": \"संपीड़न विकल्प\",\n    \"compressionSettings\": \"संपीड़न सेटिंग्स\",\n    \"description\": \"PDF फ़ाइल आकार कम करें।\",\n    \"errorCompressingPdf\": \"पीडीएफ संपीड़ित करने में विफल: {{error}}\",\n    \"errorReadingPdf\": \"PDF फ़ाइल पढ़ने में विफल। कृपया सुनिश्चित करें कि यह एक मान्य PDF है।\",\n    \"fileSize\": \"मूल फ़ाइल आकार\",\n    \"high\": \"उच्च\",\n    \"highCompression\": \"उच्च संपीड़न\",\n    \"highCompressionDescription\": \"कुछ गुणवत्ता हानि के साथ अधिकतम फ़ाइल आकार में कमी\",\n    \"imageQuality\": \"छवि गुणवत्ता\",\n    \"inputTitle\": \"इनपुट PDF\",\n    \"longDescription\": \"Ghostscript का उपयोग करके अपने ब्राउज़र में PDF फ़ाइलों को सुरक्षित रूप से संपीड़ित करें। आपकी फ़ाइलें आपके डिवाइस से कभी बाहर नहीं जातीं, जिससे पूर्ण गोपनीयता सुनिश्चित होती है और ईमेल साझा करने, वेबसाइटों पर अपलोड करने या संग्रहण स्थान बचाने के लिए फ़ाइल का आकार कम हो जाता है। WebAssembly तकनीक द्वारा संचालित।\",\n    \"low\": \"कम\",\n    \"lowCompression\": \"कम संपीड़न\",\n    \"lowCompressionDescription\": \"न्यूनतम गुणवत्ता हानि के साथ फ़ाइल आकार को थोड़ा कम करें\",\n    \"medium\": \"मध्यम\",\n    \"mediumCompression\": \"मध्यम संपीड़न\",\n    \"mediumCompressionDescription\": \"फ़ाइल आकार और गुणवत्ता के बीच संतुलन\",\n    \"pages\": \"पृष्ठों की संख्या\",\n    \"qualityPlaceholder\": \"गुणवत्ता (1-100)\",\n    \"removeMetadata\": \"मेटाडेटा हटाएं\",\n    \"resultTitle\": \"संपीड़ित PDF\",\n    \"shortDescription\": \"अपने ब्राउज़र में सुरक्षित रूप से पीडीएफ फ़ाइलों को संपीड़ित करें\",\n    \"title\": \"PDF संपीड़ित करें\"\n  },\n  \"editor\": {\n    \"description\": \"एनोटेशन, फ़ॉर्म-फ़िल, हाइलाइट और निर्यात क्षमताओं वाला उन्नत PDF संपादक। टेक्स्ट इंसर्शन, ड्रॉइंग, हाइलाइटिंग, हस्ताक्षर और फ़ॉर्म भरने जैसे पेशेवर-स्तरीय टूल से सीधे ब्राउज़र में अपनी PDF फ़ाइलों को संपादित करें।\",\n    \"shortDescription\": \"उन्नत एनोटेशन, हस्ताक्षर और संपादन टूल के साथ PDF संपादित करें\",\n    \"title\": \"पीडीएफ संपादक\"\n  },\n  \"merge\": {\n    \"inputTitle\": \"इनपुट PDF\",\n    \"loadingText\": \"पृष्ठ निकालना\",\n    \"resultTitle\": \"आउटपुट मर्ज किया गया PDF\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको कई PDF फ़ाइलों को एक ही दस्तावेज़ में मर्ज करने की सुविधा देता है। इस टूल का इस्तेमाल करने के लिए, बस उन PDF फ़ाइलों को अपलोड करें जिन्हें आप मर्ज करना चाहते हैं। फिर यह टूल इनपुट फ़ाइलों के सभी पृष्ठों को एक ही PDF दस्तावेज़ में मर्ज कर देगा।\",\n      \"title\": \"मर्ज पीडीएफ टूल का उपयोग कैसे करें?\"\n    }\n  },\n  \"mergePdf\": {\n    \"customOrder\": \"कस्टम क्रम\",\n    \"description\": \"कई PDF फ़ाइलों को एक दस्तावेज़ में जोड़ें।\",\n    \"includeBookmarks\": \"बुकमार्क शामिल करें\",\n    \"inputTitle\": \"इनपुट PDF फ़ाइलें\",\n    \"mergeOptions\": \"मर्ज विकल्प\",\n    \"mergeOrder\": \"मर्ज क्रम\",\n    \"mergingPdfs\": \"PDF को मर्ज करना\",\n    \"orderByFilename\": \"फ़ाइल नाम के अनुसार क्रम\",\n    \"orderByUpload\": \"अपलोड क्रम के अनुसार\",\n    \"pdfOptions\": \"पीडीएफ विकल्प\",\n    \"resultTitle\": \"मर्ज किया गया PDF\",\n    \"shortDescription\": \"कई पीडीएफ फ़ाइलों को एक दस्तावेज़ में मर्ज करें\",\n    \"sortByFileName\": \"फ़ाइल नाम के अनुसार क्रमबद्ध करें\",\n    \"sortByFileNameDescription\": \"PDF को फ़ाइल नाम के अनुसार वर्णानुक्रम में क्रमबद्ध करें\",\n    \"sortByUploadOrder\": \"अपलोड क्रम के अनुसार क्रमबद्ध करें\",\n    \"sortByUploadOrderDescription\": \"PDF को उसी क्रम में रखें जिस क्रम में उन्हें अपलोड किया गया था\",\n    \"title\": \"PDF मर्ज करें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको कई PDF फ़ाइलों को एक ही दस्तावेज़ में संयोजित करने की सुविधा देता है। आप PDF फ़ाइलों को क्रमबद्ध करने का तरीका चुन सकते हैं और यह टूल उन्हें निर्दिष्ट क्रम में मर्ज कर देगा।\",\n      \"title\": \"पीडीएफ फाइलों को मर्ज करें\"\n    }\n  },\n  \"pdfToEpub\": {\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"description\": \"बेहतर ई-रीडर संगतता के लिए PDF दस्तावेज़ों को EPUB फ़ाइलों में बदलें।\",\n    \"extractImages\": \"छवियां निकालें\",\n    \"generateToc\": \"सामग्री तालिका उत्पन्न करें\",\n    \"inputTitle\": \"इनपुट PDF\",\n    \"preserveFormatting\": \"फॉर्मेटिंग संरक्षित करें\",\n    \"resultTitle\": \"EPUB फ़ाइल\",\n    \"shortDescription\": \"पीडीएफ फ़ाइलों को ईपीयूबी प्रारूप में बदलें\",\n    \"title\": \"PDF से EPUB\"\n  },\n  \"pdfToPng\": {\n    \"description\": \"पीडीएफ दस्तावेजों को पीएनजी पैनल में बदलें।\",\n    \"longDescription\": \"एक PDF अपलोड करें और प्रत्येक पृष्ठ को सीधे अपने ब्राउज़र में एक उच्च-गुणवत्ता वाली PNG छवि में बदलें। यह टूल विज़ुअल सामग्री निकालने या अलग-अलग पृष्ठों को साझा करने के लिए आदर्श है। कोई डेटा अपलोड नहीं किया जाता है - सब कुछ स्थानीय रूप से चलता है।\",\n    \"shortDescription\": \"PDF को PNG छवियों में परिवर्तित करें\",\n    \"title\": \"पीडीएफ से पीएनजी\"\n  },\n  \"convertToPdf\": {\n    \"title\": \"PDF में छवि बदलें\",\n    \"description\": \"विभिन्न छवि प्रारूपों (PNG, GIF, JPG, TIF, PSD, SVG, WEBP, HEIC, RAW) को PDF में परिवर्तित करें और छवि का आकार और पृष्ठ की अभिविन्यास समायोजित करें।\",\n    \"shortDescription\": \"छवियों को PDF में परिवर्तित करें, आकार और अभिविन्यास समायोजित करने योग्य\"\n  },\n  \"protectPdf\": {\n    \"allowCopying\": \"कॉपी करने की अनुमति दें\",\n    \"allowModification\": \"संशोधन की अनुमति दें\",\n    \"allowPrinting\": \"प्रिंटिंग की अनुमति दें\",\n    \"description\": \"PDF फ़ाइलों में पासवर्ड सुरक्षा जोड़ें।\",\n    \"inputTitle\": \"इनपुट PDF\",\n    \"ownerPassword\": \"मालिक पासवर्ड\",\n    \"ownerPasswordPlaceholder\": \"पासवर्ड\",\n    \"permissions\": \"अनुमतियां\",\n    \"protectionOptions\": \"सुरक्षा विकल्प\",\n    \"resultTitle\": \"सुरक्षित PDF\",\n    \"shortDescription\": \"पीडीएफ फ़ाइलों को सुरक्षित रूप से पासवर्ड सुरक्षित करें\",\n    \"title\": \"PDF सुरक्षित करें\",\n    \"userPassword\": \"उपयोगकर्ता पासवर्ड\",\n    \"userPasswordPlaceholder\": \"पासवर्ड\"\n  },\n  \"rotatePdf\": {\n    \"allPagesWillBeRotated\": \"सभी {{count}} पृष्ठ घुमाए जाएंगे\",\n    \"angle180\": \"180 डिग्री\",\n    \"angle270\": \"270 डिग्री\",\n    \"angle90\": \"90 डिग्री\",\n    \"angleOptions\": {\n      \"clockwise90\": \"90° दक्षिणावर्त\",\n      \"counterClockwise270\": \"270° (90° वामावर्त)\",\n      \"upsideDown180\": \"180° (उल्टा)\"\n    },\n    \"applyToAll\": \"सभी पेजों पर लागू करें\",\n    \"applyToAllPages\": \"सभी पृष्ठों पर लागू करें\",\n    \"applyToSelected\": \"चयनित पेजों पर लागू करें\",\n    \"description\": \"PDF पेजों को घुमाएं।\",\n    \"inputTitle\": \"इनपुट PDF\",\n    \"longDescription\": \"PDF पृष्ठों को 90, 180, या 270 डिग्री घुमाकर उनका ओरिएंटेशन बदलें। गलत तरीके से स्कैन किए गए दस्तावेज़ों को ठीक करने या PDF को प्रिंट करने के लिए तैयार करने में उपयोगी।\",\n    \"pageRangesDescription\": \"पृष्ठ संख्या या श्रेणियाँ अल्पविराम से अलग करके दर्ज करें (उदाहरण के लिए, 1,3,5-7)\",\n    \"pageRangesPlaceholder\": \"उदाहरणार्थ, 1,5-8\",\n    \"pagesPlaceholder\": \"जैसे 1,3-5,7\",\n    \"pagesWillBeRotated\": \"{{count}} पृष्ठ{{count !== 1 ? 's' : ''}} घुमाया जाएगा\",\n    \"pdfPageCount\": \"पीडीएफ में है {{count}} पृष्ठ{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"घुमाया गया PDF\",\n    \"rotatingPages\": \"घूमते हुए पृष्ठ\",\n    \"rotationAngle\": \"घुमाने का कोण\",\n    \"rotationOptions\": \"घुमाने के विकल्प\",\n    \"rotationSettings\": \"रोटेशन सेटिंग्स\",\n    \"selectedPages\": \"चयनित पेज\",\n    \"shortDescription\": \"PDF दस्तावेज़ में पृष्ठों को घुमाएँ\",\n    \"title\": \"PDF घुमाएं\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको PDF दस्तावेज़ में पृष्ठों को घुमाने की सुविधा देता है। आप सभी पृष्ठों को घुमा सकते हैं या घुमाने के लिए अलग-अलग पृष्ठ निर्दिष्ट कर सकते हैं। घुमाव कोण चुनें: 90° दक्षिणावर्त, 180° (उल्टा), या 270° (90° वामावर्त)। विशिष्ट पृष्ठों को घुमाने के लिए, \\\"सभी पृष्ठों पर लागू करें\\\" को अनचेक करें और पृष्ठ संख्याएँ या अल्पविराम से अलग की गई श्रेणियाँ (जैसे, 1,3,5-7) दर्ज करें।\",\n      \"title\": \"रोटेट पीडीएफ टूल का उपयोग कैसे करें\"\n    }\n  },\n  \"splitPdf\": {\n    \"description\": \"PDF फ़ाइल से विशिष्ट पेज निकालें।\",\n    \"extractingPages\": \"पृष्ठ निकालना\",\n    \"includeBookmarks\": \"बुकमार्क शामिल करें\",\n    \"inputTitle\": \"इनपुट PDF\",\n    \"pageExtractionPreview\": \"{{count}} पृष्ठ{{count !== 1 ? 's' : ''}} निकाला जाएगा\",\n    \"pageRanges\": \"पेज श्रेणियां\",\n    \"pageRangesDescription\": \"पृष्ठ संख्या या श्रेणियाँ अल्पविराम से अलग करके दर्ज करें (उदाहरण के लिए, 1,3,5-7)\",\n    \"pageRangesPlaceholder\": \"उदाहरणार्थ, 1,5-8\",\n    \"pageSelection\": \"पृष्ठ चयन\",\n    \"pdfPageCount\": \"पीडीएफ में है {{count}} पृष्ठ{{count !== 1 ? 's' : ''}}\",\n    \"rangesPlaceholder\": \"जैसे 1,3-5,7\",\n    \"resultTitle\": \"विभाजित PDF फ़ाइलें\",\n    \"shortDescription\": \"पीडीएफ फ़ाइल से विशिष्ट पेज निकालें\",\n    \"splitByBookmarks\": \"बुकमार्क द्वारा विभाजित करें\",\n    \"splitByPages\": \"पेज द्वारा विभाजित करें\",\n    \"splitByRanges\": \"श्रेणियों द्वारा विभाजित करें\",\n    \"splitMethod\": \"विभाजन विधि\",\n    \"splitOptions\": \"विभाजन विकल्प\",\n    \"title\": \"PDF विभाजित करें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको किसी PDF दस्तावेज़ से विशिष्ट पृष्ठ निकालने की सुविधा देता है। आप निकालने के लिए अलग-अलग पृष्ठ या पृष्ठों की श्रेणी निर्दिष्ट कर सकते हैं।\",\n      \"title\": \"PDF विभाजित करें\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/string.json",
    "content": "{\n  \"base64\": {\n    \"decode\": \"बेस64 डिकोड\",\n    \"description\": \"बेस64 एनकोडिंग का उपयोग करके टेक्स्ट को एनकोड या डिकोड करें।\",\n    \"encode\": \"बेस64 एनकोड\",\n    \"inputTitle\": \"इनपुट डेटा\",\n    \"optionsTitle\": \"बेस64 विकल्प\",\n    \"resultTitle\": \"परिणाम\",\n    \"shortDescription\": \"बेस64 का उपयोग करके डेटा को एनकोड या डिकोड करें।\",\n    \"title\": \"बेस64 एनकोडर/डिकोडर\",\n    \"toolInfo\": {\n      \"description\": \"बेस64 एक एनकोडिंग योजना है जो डेटा को रेडिक्स-64 प्रतिनिधित्व में अनुवाद करके ASCII स्ट्रिंग प्रारूप में प्रस्तुत करती है। हालांकि इसका उपयोग स्ट्रिंग्स को एनकोड करने के लिए किया जा सकता है, यह आमतौर पर बाइनरी डेटा को एनकोड करने के लिए उपयोग किया जाता है जो टेक्स्ट डेटा से निपटने के लिए डिज़ाइन किए गए मीडिया पर प्रसारण के लिए होता है।\",\n      \"title\": \"बेस64 क्या है?\"\n    }\n  },\n  \"censor\": {\n    \"description\": \"पाठ में शब्दों को सेंसर करने की उपयोगिता। बाईं ओर दिए गए इनपुट फ़ॉर्म में अपना पाठ लोड करें, विकल्पों में सभी गलत शब्द निर्दिष्ट करें, और आपको आउटपुट क्षेत्र में तुरंत सेंसर किया गया पाठ मिल जाएगा।\\\", longDescription: 'इस ऑनलाइन टूल से, आप किसी भी पाठ में कुछ शब्दों को सेंसर कर सकते हैं। आप अवांछित शब्दों (जैसे अपशब्द या गुप्त शब्द) की एक सूची निर्दिष्ट कर सकते हैं और प्रोग्राम उन्हें वैकल्पिक शब्दों से बदलकर एक सुरक्षित पाठ तैयार कर देगा। विकल्पों में एक बहु-पंक्ति पाठ फ़ील्ड में प्रति पंक्ति एक शब्द दर्ज करके शब्दों को निर्दिष्ट किया जा सकता है।', keywords: ['text', 'censor', 'words', 'characters'], component: lazy(() => import('./index')), i18n: { name: 'string:censor.title', description: 'string:censor.description'\",\n    \"shortDescription\": \"बुरे शब्दों को तुरंत हटा दें या उन्हें वैकल्पिक शब्दों से बदल दें।\",\n    \"title\": \"पाठ सेंसर\"\n  },\n  \"createPalindrome\": {\n    \"description\": \"किसी भी टेक्स्ट से पैलिंड्रोम बनाने के लिए दुनिया का सबसे सरल ब्राउज़र-आधारित उपयोगिता। टेक्स्ट इनपुट करें और इसे तुरंत एक पैलिंड्रोम में बदल दें जो आगे और पीछे एक जैसा पढ़ता है। शब्द खेलों, सममित टेक्स्ट पैटर्न बनाने, या भाषाई जिज्ञासाओं की खोज के लिए बिल्कुल सही।\",\n    \"shortDescription\": \"ऐसा टेक्स्ट बनाएं जो आगे और पीछे एक जैसा पढ़ता है\",\n    \"title\": \"पैलिंड्रोम बनाएं\"\n  },\n  \"extractSubstring\": {\n    \"description\": \"टेक्स्ट से सबस्ट्रिंग निकालने के लिए दुनिया की सबसे सरल ब्राउज़र-आधारित उपयोगिता। अपना टेक्स्ट इनपुट करें और वांछित भाग निकालने के लिए आरंभ और अंत स्थितियाँ निर्दिष्ट करें। डेटा प्रोसेसिंग, टेक्स्ट विश्लेषण, या बड़े टेक्स्ट ब्लॉक से विशिष्ट सामग्री निकालने के लिए बिल्कुल सही।\",\n    \"shortDescription\": \"निर्दिष्ट स्थानों के बीच पाठ का एक भाग निकालें\",\n    \"title\": \"सबस्ट्रिंग निकालें\"\n  },\n  \"hiddenCharacterDetector\": {\n    \"analysisOptions\": \"विश्लेषण विकल्प\",\n    \"category\": \"वर्ग\",\n    \"description\": \"छिपे हुए यूनिकोड वर्णों का पता लगाएं, विशेष रूप से RTL ओवरराइड वर्णों का, जिनका उपयोग हमलों में किया जा सकता है।\",\n    \"foundChars\": \"मिला {{count}} छिपे हुए अक्षर:\",\n    \"inputPlaceholder\": \"छिपे हुए वर्णों की जांच करने के लिए पाठ दर्ज करें...\",\n    \"inputTitle\": \"विश्लेषण करने के लिए पाठ\",\n    \"invisibleChar\": \"अदृश्य चरित्र\",\n    \"invisibleFound\": \"अदृश्य अक्षर मिले\",\n    \"longDescription\": \"यह टूल आपको टेक्स्ट में छिपे यूनिकोड वर्णों, खासकर दाएँ से बाएँ (RTL) ओवरराइड वर्णों का पता लगाने में मदद करता है, जिनका इस्तेमाल हमलों में किया जा सकता है। यह अदृश्य वर्णों, शून्य-चौड़ाई वाले वर्णों और अन्य संभावित रूप से दुर्भावनापूर्ण यूनिकोड अनुक्रमों की पहचान कर सकता है जो दिखने में निर्दोष टेक्स्ट में छिपे हो सकते हैं।\",\n    \"noHiddenChars\": \"पाठ में कोई छिपा हुआ अक्षर नहीं पाया गया।\",\n    \"optionsDescription\": \"कॉन्फ़िगर करें कि किस प्रकार के छिपे हुए वर्णों का पता लगाना है और परिणाम कैसे प्रदर्शित करने हैं।\",\n    \"position\": \"पद\",\n    \"rtlAlert\": \"⚠️ RTL ओवरराइड वर्णों का पता चला! इस पाठ में दुर्भावनापूर्ण छिपे हुए वर्ण हो सकते हैं।\",\n    \"rtlFound\": \"RTL ओवरराइड पाया गया\",\n    \"rtlOverride\": \"RTL ओवरराइड कैरेक्टर\",\n    \"rtlWarning\": \"चेतावनी: RTL ओवरराइड वर्णों का पता चला है! इसका इस्तेमाल हमलों में किया जा सकता है।\",\n    \"shortDescription\": \"पाठ में छिपे यूनिकोड वर्णों को ढूंढें\",\n    \"summary\": \"विश्लेषण सारांश\",\n    \"title\": \"छिपे हुए चरित्र डिटेक्टर\",\n    \"totalChars\": \"कुल छिपे हुए अक्षर: {{count}}\",\n    \"unicode\": \"यूनिकोड\",\n    \"zeroWidthChar\": \"शून्य चौड़ाई वर्ण\",\n    \"zeroWidthFound\": \"शून्य-चौड़ाई वाले वर्ण मिले\"\n  },\n  \"join\": {\n    \"blankLinesAndTrailingSpaces\": \"खाली पंक्तियां और अनुगामी स्थान\",\n    \"deleteBlankDescription\": \"उन पंक्तियों को हटाएं जिनमें टेक्स्ट प्रतीक नहीं हैं।\",\n    \"deleteBlankTitle\": \"खाली पंक्तियां हटाएं\",\n    \"deleteTrailingDescription\": \"पंक्तियों के अंत से स्पेस और टैब हटाएं।\",\n    \"deleteTrailingTitle\": \"अनुगामी स्थान हटाएं\",\n    \"description\": \"कस्टमाइज़ करने योग्य विभाजकों के साथ टेक्स्ट टुकड़ों को एक साथ जोड़ें।\",\n    \"inputTitle\": \"टेक्स्ट टुकड़े\",\n    \"joinCharacterDescription\": \"प्रतीक जो टेक्स्ट के टूटे हुए टुकड़ों को जोड़ता है। (डिफ़ॉल्ट रूप से स्पेस।)\",\n    \"joinCharacterPlaceholder\": \"जोड़ने का वर्ण\",\n    \"resultTitle\": \"जुड़ा हुआ टेक्स्ट\",\n    \"shortDescription\": \"निर्दिष्ट विभाजक के साथ पाठ तत्वों को जोड़ें\",\n    \"textMergedOptions\": \"टेक्स्ट मर्ज विकल्प\",\n    \"title\": \"टेक्स्ट जोड़ें\",\n    \"toolInfo\": {\n      \"description\": \"इस टूल के साथ आप टेक्स्ट के भागों को एक साथ जोड़ सकते हैं। यह नई पंक्तियों से अलग किए गए टेक्स्ट मूल्यों की सूची लेता है और उन्हें एक साथ मर्ज करता है। आप उस वर्ण को सेट कर सकते हैं जो संयुक्त टेक्स्ट के भागों के बीच रखा जाएगा। साथ ही, आप सभी खाली पंक्तियों को अनदेखा कर सकते हैं और सभी पंक्तियों के अंत से स्पेस और टैब हटा सकते हैं। टेक्स्टाबुलस!\",\n      \"title\": \"टेक्स्ट जोइनर क्या है?\"\n    }\n  },\n  \"palindrome\": {\n    \"description\": \"यह जांचने के लिए दुनिया का सबसे सरल ब्राउज़र-आधारित उपयोगिता कि टेक्स्ट पैलिंड्रोम है या नहीं। तुरंत सत्यापित करें कि क्या आपका टेक्स्ट आगे और पीछे एक जैसा पढ़ता है। शब्द पहेलियों, भाषाई विश्लेषण, या सममित टेक्स्ट पैटर्न को मान्य करने के लिए बिल्कुल सही। विभिन्न विभाजकों और बहु-शब्द पैलिंड्रोम पहचान का समर्थन करता है।\",\n    \"shortDescription\": \"जांचें कि क्या टेक्स्ट आगे और पीछे एक जैसा पढ़ता है\",\n    \"title\": \"पैलिंड्रोम\"\n  },\n  \"passwordGenerator\": {\n    \"avoidAmbiguous\": \"अस्पष्ट वर्णों (i, I, l, 0, O) से बचें\",\n    \"description\": \"अनुकूलन योग्य लंबाई और वर्ण प्रकारों के साथ सुरक्षित, यादृच्छिक पासवर्ड बनाएँ। लोअरकेस, अपरकेस, संख्याएँ और विशेष वर्णों में से चुनें। बेहतर पठनीयता के लिए अस्पष्ट वर्णों से बचने का विकल्प।\",\n    \"includeLowercase\": \"छोटे अक्षर (a-z) शामिल करें\",\n    \"includeNumbers\": \"संख्याएँ शामिल करें (0-9)\",\n    \"includeSymbols\": \"विशेष वर्ण शामिल करें\",\n    \"includeUppercase\": \"बड़े अक्षर (A-Z) शामिल करें\",\n    \"lengthDesc\": \"पासवर्ड की लंबाई\",\n    \"lengthPlaceholder\": \"उदाहरणार्थ 12\",\n    \"optionsTitle\": \"पासवर्ड विकल्प\",\n    \"resultTitle\": \"जनरेट किया गया पासवर्ड\",\n    \"shortDescription\": \"कस्टम विकल्पों के साथ सुरक्षित यादृच्छिक पासवर्ड उत्पन्न करें\",\n    \"title\": \"पासवर्ड जनरेटर\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपके चुने हुए मानदंडों के आधार पर सुरक्षित, यादृच्छिक पासवर्ड बनाता है। आप लंबाई को अनुकूलित कर सकते हैं, विभिन्न प्रकार के वर्णों को शामिल या हटा सकते हैं, और बेहतर पठनीयता के लिए अस्पष्ट वर्णों से बच सकते हैं। खातों, एप्लिकेशन या किसी भी सुरक्षा आवश्यकताओं के लिए मज़बूत पासवर्ड बनाने के लिए यह एकदम सही है।\",\n      \"title\": \"पासवर्ड जनरेटर के बारे में\"\n    }\n  },\n  \"quote\": {\n    \"allowDoubleQuotation\": \"दोहरे उद्धरण की अनुमति दें\",\n    \"description\": \"कस्टमाइज़ करने योग्य विकल्पों के साथ टेक्स्ट के चारों ओर उद्धरण जोड़ें।\",\n    \"inputTitle\": \"इनपुट टेक्स्ट\",\n    \"leftQuoteDescription\": \"बाएं उद्धरण वर्ण\",\n    \"processAsMultiLine\": \"बहु-पंक्ति टेक्स्ट के रूप में प्रक्रिया करें\",\n    \"quoteEmptyLines\": \"खाली पंक्तियों को कोट करें\",\n    \"quoteOptions\": \"उद्धरण विकल्प\",\n    \"resultTitle\": \"कोटेड टेक्स्ट\",\n    \"rightQuoteDescription\": \"दाएं उद्धरण वर्ण\",\n    \"shortDescription\": \"विभिन्न शैलियों के साथ पाठ के चारों ओर उद्धरण जोड़ें\",\n    \"title\": \"टेक्स्ट कोटर\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको टेक्स्ट के चारों ओर उद्धरण जोड़ने की अनुमति देता है। आप विभिन्न उद्धरण वर्ण चुन सकते हैं, बहु-पंक्ति टेक्स्ट को संभाल सकते हैं, और नियंत्रित कर सकते हैं कि खाली पंक्तियों को कैसे संसाधित किया जाता है। यह प्रोग्रामिंग के लिए टेक्स्ट तैयार करने, डेटा को फॉर्मेट करने, या स्टाइलिश टेक्स्ट बनाने के लिए उपयोगी है।\",\n      \"title\": \"टेक्स्ट कोटर\"\n    }\n  },\n  \"randomizeCase\": {\n    \"description\": \"टेक्स्ट केस को रैंडमाइज़ करने के लिए दुनिया की सबसे सरल ब्राउज़र-आधारित उपयोगिता। अपना टेक्स्ट इनपुट करें और उसे तुरंत रैंडम अपर और लोअर केस अक्षरों से बदल दें। अनोखे टेक्स्ट इफ़ेक्ट बनाने, केस सेंसिटिविटी टेस्ट करने, या अलग-अलग टेक्स्ट पैटर्न बनाने के लिए बिल्कुल सही।\",\n    \"shortDescription\": \"पाठ में अक्षरों के केस को यादृच्छिक करें\",\n    \"title\": \"मामले को यादृच्छिक करें\"\n  },\n  \"removeDuplicateLines\": {\n    \"description\": \"बाईं ओर दिए गए इनपुट फ़ॉर्म में अपना टेक्स्ट लोड करें और आपको तुरंत ऐसा टेक्स्ट मिलेगा जिसमें आउटपुट क्षेत्र में कोई डुप्लिकेट लाइन नहीं होगी। शक्तिशाली, मुफ़्त और तेज़। टेक्स्ट लाइन लोड करें - अनोखी टेक्स्ट लाइन प्राप्त करें\",\n    \"shortDescription\": \"पाठ से सभी दोहराई गई पंक्तियों को तुरंत हटाएँ\",\n    \"title\": \"डुप्लिकेट पंक्तियाँ हटाएँ\"\n  },\n  \"repeat\": {\n    \"delimiterDescription\": \"आउटपुट प्रतियों के लिए विभाजक।\",\n    \"delimiterPlaceholder\": \"विभाजक\",\n    \"description\": \"कस्टमाइज़ करने योग्य विभाजकों के साथ टेक्स्ट को कई बार दोहराएं।\",\n    \"inputTitle\": \"इनपुट टेक्स्ट\",\n    \"numberPlaceholder\": \"संख्या\",\n    \"repeatAmountDescription\": \"दोहराव की संख्या।\",\n    \"repetitionsDelimiter\": \"दोहराव विभाजक\",\n    \"resultTitle\": \"दोहराया गया टेक्स्ट\",\n    \"shortDescription\": \"टेक्स्ट को कई बार दोहराएं\",\n    \"textRepetitions\": \"टेक्स्ट दोहराव\",\n    \"title\": \"टेक्स्ट दोहराएं\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको वैकल्पिक विभाजक के साथ दिए गए टेक्स्ट को कई बार दोहराने की अनुमति देता है।\",\n      \"title\": \"टेक्स्ट दोहराएं\"\n    }\n  },\n  \"reverse\": {\n    \"description\": \"टेक्स्ट में वर्णों के क्रम को उलटा करें।\",\n    \"inputTitle\": \"इनपुट टेक्स्ट\",\n    \"processMultiLine\": \"बहु-पंक्ति टेक्स्ट के रूप में प्रक्रिया करें (प्रत्येक पंक्ति को अलग से उलटा करें)\",\n    \"processMultiLineDescription\": \"प्रत्येक पंक्ति स्वतंत्र रूप से उलटी जाएगी\",\n    \"resultTitle\": \"उलटा टेक्स्ट\",\n    \"reversalOptions\": \"उलट विकल्प\",\n    \"shortDescription\": \"किसी भी टेक्स्ट को वर्ण दर वर्ण उलटा करें\",\n    \"skipEmptyLines\": \"खाली पंक्तियों को छोड़ें\",\n    \"skipEmptyLinesDescription\": \"आउटपुट से खाली लाइनें हटा दी जाएंगी\",\n    \"title\": \"टेक्स्ट उलटा करें\",\n    \"trimWhitespace\": \"पंक्तियों से सफेद स्थान ट्रिम करें\",\n    \"trimWhitespaceDescription\": \"प्रत्येक पंक्ति से आरंभिक और अंतिम रिक्त स्थान हटाएँ\"\n  },\n  \"rot13\": {\n    \"description\": \"ROT13 सिफर का उपयोग करके टेक्स्ट को एनकोड या डिकोड करें।\",\n    \"inputTitle\": \"इनपुट टेक्स्ट\",\n    \"resultTitle\": \"ROT13 परिणाम\",\n    \"shortDescription\": \"ROT13 सिफर का उपयोग करके टेक्स्ट को एनकोड या डिकोड करें।\",\n    \"title\": \"ROT13 एनकोडर/डिकोडर\",\n    \"toolInfo\": {\n      \"description\": \"ROT13 (13 स्थानों से घुमाएं) एक सरल अक्षर प्रतिस्थापन सिफर है जो एक अक्षर को वर्णमाला में उसके बाद के 13वें अक्षर से बदल देता है। ROT13 सीज़र सिफर का एक विशेष मामला है जो प्राचीन रोम में विकसित किया गया था। क्योंकि अंग्रेजी वर्णमाला में 26 अक्षर हैं, ROT13 अपना स्वयं का व्युत्क्रम है; अर्थात, ROT13 को पूर्ववत करने के लिए, एक ही एल्गोरिथम लागू किया जाता है, इसलिए एनकोडिंग और डिकोडिंग दोनों के लिए एक ही क्रिया का उपयोग किया जा सकता है।\",\n      \"title\": \"ROT13 क्या है?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"निर्दिष्ट स्थानों द्वारा टेक्स्ट में वर्णों को घुमाएं।\",\n    \"inputTitle\": \"इनपुट टेक्स्ट\",\n    \"processAsMultiLine\": \"बहु-पंक्ति टेक्स्ट के रूप में प्रक्रिया करें (प्रत्येक पंक्ति को अलग से घुमाएं)\",\n    \"resultTitle\": \"घुमाया गया टेक्स्ट\",\n    \"rotateLeft\": \"बाईं ओर घुमाएं\",\n    \"rotateRight\": \"दाईं ओर घुमाएं\",\n    \"rotationOptions\": \"घुमाने के विकल्प\",\n    \"shortDescription\": \"पाठ में वर्णों को स्थान के अनुसार स्थानांतरित करें।\",\n    \"stepDescription\": \"घुमाने के लिए स्थानों की संख्या\",\n    \"title\": \"टेक्स्ट घुमाएं\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको निर्दिष्ट संख्या में स्थानों द्वारा स्ट्रिंग में वर्णों को घुमाने की अनुमति देता है। आप बाएं या दाएं घुमा सकते हैं, और प्रत्येक पंक्ति को अलग से घुमाकर बहु-पंक्ति टेक्स्ट को संसाधित कर सकते हैं। स्ट्रिंग रोटेशन सरल टेक्स्ट परिवर्तनों, पैटर्न बनाने, या बुनियादी एन्क्रिप्शन तकनीकों को लागू करने के लिए उपयोगी है।\",\n      \"title\": \"स्ट्रिंग रोटेशन\"\n    }\n  },\n  \"split\": {\n    \"charAfterChunkDescription\": \"प्रत्येक चंक के बाद वर्ण\",\n    \"charBeforeChunkDescription\": \"प्रत्येक चंक से पहले वर्ण\",\n    \"chunksDescription\": \"आउटपुट में समान लंबाई के चंक की संख्या।\",\n    \"chunksTitle\": \"चंक की संख्या का उपयोग करें\",\n    \"description\": \"विभिन्न मानदंडों के आधार पर टेक्स्ट को भागों में विभाजित करें।\",\n    \"lengthDescription\": \"प्रत्येक आउटपुट चंक में रखे जाने वाले वर्णों की संख्या।\",\n    \"lengthTitle\": \"विभाजन के लिए लंबाई का उपयोग करें\",\n    \"outputSeparatorDescription\": \"वर्ण जो विभाजित चंक के बीच रखा जाएगा। (यह डिफ़ॉल्ट रूप से नई पंक्ति \\\"\\\\n\\\" है।)\",\n    \"outputSeparatorOptions\": \"आउटपुट विभाजक विकल्प\",\n    \"regexDescription\": \"नियमित अभिव्यक्ति जो टेक्स्ट को भागों में तोड़ने के लिए उपयोग की जाएगी। (डिफ़ॉल्ट रूप से कई स्पेस।)\",\n    \"regexTitle\": \"विभाजन के लिए रेगेक्स का उपयोग करें\",\n    \"resultTitle\": \"टेक्स्ट टुकड़े\",\n    \"shortDescription\": \"विभाजक का उपयोग करके पाठ को कई भागों में विभाजित करें\",\n    \"splitSeparatorOptions\": \"विभाजक विकल्प\",\n    \"symbolDescription\": \"वर्ण जो टेक्स्ट को भागों में तोड़ने के लिए उपयोग किया जाएगा। (डिफ़ॉल्ट रूप से स्पेस।)\",\n    \"symbolTitle\": \"विभाजन के लिए प्रतीक का उपयोग करें\",\n    \"title\": \"टेक्स्ट विभाजित करें\"\n  },\n  \"statistic\": {\n    \"characterFrequencyAnalysis\": \"वर्ण आवृत्ति विश्लेषण\",\n    \"characterFrequencyAnalysisDescription\": \"गणना करें कि प्रत्येक वर्ण टेक्स्ट में कितनी बार दिखाई देता है\",\n    \"delimitersOptions\": \"विभाजक विकल्प\",\n    \"description\": \"टेक्स्ट का विश्लेषण करें और व्यापक आंकड़े उत्पन्न करें।\",\n    \"includeEmptyLines\": \"खाली पंक्तियां शामिल करें\",\n    \"includeEmptyLinesDescription\": \"पंक्तियों की गणना करते समय खाली पंक्तियां शामिल करें\",\n    \"inputTitle\": \"इनपुट टेक्स्ट\",\n    \"resultTitle\": \"टेक्स्ट आंकड़े\",\n    \"sentenceDelimitersDescription\": \"अपनी भाषा में वाक्यों को विभाजित करने के लिए उपयोग किए जाने वाले कस्टम वर्ण दर्ज करें (कॉमा से अलग) या डिफ़ॉल्ट के लिए इसे खाली छोड़ दें।\",\n    \"sentenceDelimitersPlaceholder\": \"जैसे ., !, ?, ...\",\n    \"shortDescription\": \"अपने पाठ के बारे में आँकड़े प्राप्त करें\",\n    \"statisticsOptions\": \"आंकड़े विकल्प\",\n    \"title\": \"टेक्स्ट आंकड़े\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको टेक्स्ट का विश्लेषण करने और वर्ण गणना, शब्द गणना, पंक्ति गणना, और वर्णों और शब्दों के आवृत्ति विश्लेषण सहित व्यापक आंकड़े उत्पन्न करने की अनुमति देता है।\",\n      \"title\": \"{{title}} क्या है?\"\n    },\n    \"wordDelimitersDescription\": \"शब्दों की गणना के लिए कस्टम रेगेक्स दर्ज करें या डिफ़ॉल्ट के लिए इसे खाली छोड़ दें।\",\n    \"wordDelimitersPlaceholder\": \"जैसे \\\\s.,;:!?\\\"«»()…\",\n    \"wordFrequencyAnalysis\": \"शब्द आवृत्ति विश्लेषण\",\n    \"wordFrequencyAnalysisDescription\": \"गणना करें कि प्रत्येक शब्द टेक्स्ट में कितनी बार दिखाई देता है\"\n  },\n  \"textReplacer\": {\n    \"description\": \"टेक्स्ट पैटर्न को नई सामग्री से बदलें।\",\n    \"findPatternInText\": \"टेक्स्ट में यह पैटर्न खोजें\",\n    \"findPatternUsingRegexp\": \"रेगेक्स का उपयोग करके पैटर्न खोजें\",\n    \"inputTitle\": \"बदलने के लिए टेक्स्ट\",\n    \"newTextPlaceholder\": \"नया टेक्स्ट\",\n    \"regexpDescription\": \"उस नियमित अभिव्यक्ति को दर्ज करें जिसे आप बदलना चाहते हैं।\",\n    \"replacePatternDescription\": \"प्रतिस्थापन के लिए उपयोग करने के लिए पैटर्न दर्ज करें।\",\n    \"replaceText\": \"टेक्स्ट बदलें\",\n    \"resultTitle\": \"प्रतिस्थापन के साथ टेक्स्ट\",\n    \"searchPatternDescription\": \"उस टेक्स्ट पैटर्न को दर्ज करें जिसे आप बदलना चाहते हैं।\",\n    \"searchText\": \"खोज टेक्स्ट\",\n    \"shortDescription\": \"अपनी सामग्री में टेक्स्ट को तुरंत बदलें\",\n    \"title\": \"टेक्स्ट रिप्लेसर\",\n    \"toolInfo\": {\n      \"description\": \"इस सरल, ब्राउज़र-आधारित टूल के साथ अपनी सामग्री में विशिष्ट टेक्स्ट को आसानी से बदलें। बस अपना टेक्स्ट इनपुट करें, उस टेक्स्ट को सेट करें जिसे आप बदलना चाहते हैं और प्रतिस्थापन मूल्य, और तुरंत अपडेटेड संस्करण प्राप्त करें।\",\n      \"title\": \"टेक्स्ट रिप्लेसर\"\n    }\n  },\n  \"toMorse\": {\n    \"dashSymbolDescription\": \"प्रतीक जो मोर्स कोड में डैश के अनुरूप होगा।\",\n    \"description\": \"टेक्स्ट को मोर्स कोड में बदलें।\",\n    \"dotSymbolDescription\": \"प्रतीक जो मोर्स कोड में डॉट के अनुरूप होगा।\",\n    \"longSignal\": \"लंबा संकेत\",\n    \"resultTitle\": \"मोर्स कोड\",\n    \"shortDescription\": \"टेक्स्ट को जल्दी से मोर्स में एनकोड करें\",\n    \"shortSignal\": \"छोटा संकेत\",\n    \"title\": \"मोर्स में\"\n  },\n  \"truncate\": {\n    \"addTruncationIndicator\": \"काटने का संकेतक जोड़ें\",\n    \"charactersPlaceholder\": \"वर्ण\",\n    \"description\": \"टेक्स्ट को निर्दिष्ट लंबाई तक छोटा करें।\",\n    \"indicatorDescription\": \"टेक्स्ट के अंत (या शुरुआत) में जोड़ने के लिए वर्ण। नोट: वे लंबाई की ओर गिने जाते हैं।\",\n    \"inputTitle\": \"इनपुट टेक्स्ट\",\n    \"leftSideDescription\": \"टेक्स्ट की शुरुआत से वर्ण हटाएं।\",\n    \"leftSideTruncation\": \"बाईं तरफ काटना\",\n    \"lengthAndLines\": \"लंबाई और पंक्तियां\",\n    \"lineByLineDescription\": \"प्रत्येक पंक्ति को अलग से काटें।\",\n    \"lineByLineTruncating\": \"पंक्ति दर पंक्ति काटना\",\n    \"maxLengthDescription\": \"टेक्स्ट में छोड़ने के लिए वर्णों की संख्या।\",\n    \"numberPlaceholder\": \"संख्या\",\n    \"resultTitle\": \"काटा गया टेक्स्ट\",\n    \"rightSideDescription\": \"टेक्स्ट के अंत से वर्ण हटाएं।\",\n    \"rightSideTruncation\": \"दाईं तरफ काटना\",\n    \"shortDescription\": \"पाठ को निर्दिष्ट लंबाई तक छोटा करें\",\n    \"suffixAndAffix\": \"प्रत्यय और उपसर्ग\",\n    \"title\": \"टेक्स्ट काटें\",\n    \"toolInfo\": {\n      \"description\": \"बाईं ओर इनपुट फॉर्म में अपना टेक्स्ट लोड करें और आपको दाईं ओर स्वचालित रूप से काटा गया टेक्स्ट मिलेगा।\",\n      \"title\": \"टेक्स्ट काटें\"\n    },\n    \"truncationSide\": \"काटने की तरफ\"\n  },\n  \"uppercase\": {\n    \"description\": \"टेक्स्ट को बड़े अक्षरों में बदलें।\",\n    \"inputTitle\": \"इनपुट टेक्स्ट\",\n    \"resultTitle\": \"बड़े अक्षरों में टेक्स्ट\",\n    \"shortDescription\": \"पाठ को बड़े अक्षरों में बदलें\",\n    \"title\": \"बड़े अक्षरों में बदलें\"\n  },\n  \"urlDecode\": {\n    \"inputTitle\": \"इनपुट स्ट्रिंग(URL-एस्केप)\",\n    \"resultTitle\": \"आउटपुट स्ट्रिंग\",\n    \"toolInfo\": {\n      \"description\": \"अपनी स्ट्रिंग लोड करें और यह स्वचालित रूप से URL-unescaped हो जाएगी।\",\n      \"longDescription\": \"यह टूल पहले से URL-एन्कोडेड स्ट्रिंग को URL-डिकोड करता है। URL-डिकोडिंग, URL-एन्कोडिंग का विपरीत ऑपरेशन है। सभी प्रतिशत-एन्कोडेड वर्ण उन वर्णों में डिकोड हो जाते हैं जिन्हें आप समझ सकते हैं। कुछ सबसे प्रसिद्ध प्रतिशत-एन्कोडेड मान हैं: स्पेस के लिए %20, कोलन के लिए %3a, स्लैश के लिए %2f, और प्रश्नवाचक चिह्न के लिए %3f। प्रतिशत चिह्न के बाद के दो अंक हेक्स में वर्ण के चार कोड मान हैं।\",\n      \"shortDescription\": \"किसी स्ट्रिंग को शीघ्रता से URL-अनस्केप करें।\",\n      \"title\": \"स्ट्रिंग URL डिकोडर\"\n    }\n  },\n  \"urlEncode\": {\n    \"encodingOption\": {\n      \"nonSpecialCharDescription\": \"यदि चयनित किया जाता है, तो इनपुट स्ट्रिंग के सभी वर्ण URL-एन्कोडिंग में परिवर्तित हो जाएंगे (केवल विशेष नहीं)।\",\n      \"nonSpecialCharPlaceholder\": \"गैर-विशेष वर्णों को एनकोड करें\",\n      \"title\": \"एन्कोडिंग विकल्प\"\n    },\n    \"inputTitle\": \"इनपुट स्ट्रिंग\",\n    \"resultTitle\": \"URL-एस्केप स्ट्रिंग\",\n    \"toolInfo\": {\n      \"description\": \"अपनी स्ट्रिंग लोड करें और यह स्वचालित रूप से URL-एस्केप हो जाएगी।\",\n      \"longDescription\": \"यह टूल एक स्ट्रिंग को URL-एनकोड करता है। विशेष URL वर्ण प्रतिशत-चिह्न एन्कोडिंग में परिवर्तित हो जाते हैं। इस एन्कोडिंग को प्रतिशत-एनकोडिंग इसलिए कहा जाता है क्योंकि प्रत्येक वर्ण का संख्यात्मक मान प्रतिशत चिह्न में परिवर्तित हो जाता है जिसके बाद दो अंकों का हेक्साडेसिमल मान आता है। हेक्स मान वर्ण के कोडपॉइंट मान के आधार पर निर्धारित होते हैं। उदाहरण के लिए, %20 में स्पेस, %3a में कोलन और %2f में स्लैश एस्केप हो जाता है। जो वर्ण विशेष नहीं हैं वे अपरिवर्तित रहते हैं। यदि आपको गैर-विशेष वर्णों को भी प्रतिशत-एनकोडिंग में बदलने की आवश्यकता है, तो हमने एक अतिरिक्त विकल्प भी जोड़ा है जिससे आप ऐसा कर सकते हैं। इस व्यवहार को सक्षम करने के लिए encode-non-special-chars विकल्प चुनें।\",\n      \"shortDescription\": \"किसी स्ट्रिंग को शीघ्रता से URL-एस्केप करें.\",\n      \"title\": \"स्ट्रिंग URL एनकोडर\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/time.json",
    "content": "{\n  \"checkLeapYears\": {\n    \"checkMultiple\": \"कई वर्ष जांचें\",\n    \"checkOptions\": \"जांच विकल्प\",\n    \"description\": \"निर्दिष्ट वर्षों को लीप वर्ष के रूप में जांचें।\",\n    \"endPlaceholder\": \"वर्ष\",\n    \"endYear\": \"अंतिम वर्ष\",\n    \"exampleDescription\": \"हमारी एक दोस्त का जन्म 29 फ़रवरी को लीप वर्ष में हुआ था और इस वजह से, उसका जन्मदिन हर 4 साल में एक बार ही आता है। चूँकि हमें कभी याद नहीं रहता कि उसका जन्मदिन कब है, इसलिए हम अपने प्रोग्राम का इस्तेमाल करके आने वाले लीप वर्षों की एक रिमाइंडर सूची बना रहे हैं। उसके अगले जन्मदिनों की सूची बनाने के लिए, हम 2025 से 2040 तक के वर्षों का एक क्रम इनपुट में लोड करते हैं और हर वर्ष की स्थिति प्राप्त करते हैं। अगर प्रोग्राम बताता है कि यह लीप वर्ष है, तो हमें पता चल जाता है कि हमें 29 फ़रवरी को जन्मदिन की पार्टी में आमंत्रित किया जाएगा।\",\n    \"exampleTitle\": \"29 फरवरी को जन्मदिन खोजें\",\n    \"inputTitle\": \"इनपुट वर्ष\",\n    \"resultTitle\": \"लीप वर्ष परिणाम\",\n    \"shortDescription\": \"जांचें कि कोई वर्ष लीप वर्ष है या नहीं\",\n    \"startPlaceholder\": \"वर्ष\",\n    \"startYear\": \"शुरुआती वर्ष\",\n    \"title\": \"लीप वर्ष जांचें\",\n    \"toolInfo\": {\n      \"description\": \"लीप वर्ष वह वर्ष होता है जिसमें कैलेंडर वर्ष को खगोलीय वर्ष के साथ समकालिक बनाए रखने के लिए एक अतिरिक्त दिन (29 फ़रवरी) होता है। लीप वर्ष हर 4 वर्ष में आते हैं, सिवाय उन वर्षों के जो 100 से विभाज्य होते हैं लेकिन 400 से नहीं।\",\n      \"title\": \"लीप वर्ष क्या है?\"\n    },\n    \"yearRange\": \"वर्ष श्रेणी\"\n  },\n  \"convertDaysToHours\": {\n    \"addHoursName\": \"घंटे का नाम जोड़ें\",\n    \"addHoursNameDescription\": \"आउटपुट मानों में स्ट्रिंग घंटे जोड़ें\",\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"decimalPlaces\": \"दशमलव स्थान\",\n    \"description\": \"दिनों की संख्या को घंटों में बदलें।\",\n    \"hoursName\": \"घंटों का नाम\",\n    \"inputTitle\": \"इनपुट दिन\",\n    \"placesPlaceholder\": \"स्थान\",\n    \"resultTitle\": \"घंटे\",\n    \"shortDescription\": \"दिनों को घंटों में बदलें\",\n    \"showBreakdown\": \"विस्तृत विवरण दिखाएं\",\n    \"title\": \"दिनों को घंटों में बदलें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको दिनों को घंटों में बदलने की सुविधा देता है। आप दिनों को संख्याओं या इकाइयों के रूप में इनपुट कर सकते हैं, और यह टूल उन्हें घंटों में बदल देगा। आप आउटपुट मानों में 'घंटे' प्रत्यय भी जोड़ सकते हैं।\",\n      \"title\": \"दिनों को घंटों में बदलें\"\n    }\n  },\n  \"convertHoursToDays\": {\n    \"addDaysName\": \"दिन का नाम जोड़ें\",\n    \"addDaysNameDescription\": \"आउटपुट मानों में स्ट्रिंग days जोड़ें\",\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"daysName\": \"दिन का नाम\",\n    \"decimalPlaces\": \"दशमलव स्थान\",\n    \"description\": \"घंटों की संख्या को दिनों में बदलें।\",\n    \"inputTitle\": \"इनपुट घंटे\",\n    \"placesPlaceholder\": \"स्थान\",\n    \"resultTitle\": \"दिन\",\n    \"shortDescription\": \"घंटों को दिनों में बदलें\",\n    \"showBreakdown\": \"विस्तृत विवरण दिखाएं\",\n    \"title\": \"घंटों को दिनों में बदलें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको घंटों को दिनों में बदलने की सुविधा देता है। आप घंटों को संख्याओं या इकाइयों के रूप में इनपुट कर सकते हैं, और यह टूल उन्हें दिनों में बदल देगा। आप आउटपुट मानों में 'दिन' प्रत्यय भी जोड़ सकते हैं।\",\n      \"title\": \"घंटों को दिनों में बदलें\"\n    }\n  },\n  \"convertSecondsToTime\": {\n    \"addPadding\": \"पैडिंग जोड़ें\",\n    \"addPaddingDescription\": \"घंटे, मिनट और सेकंड में शून्य पैडिंग जोड़ें।\",\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"description\": \"सेकंड की संख्या को पठनीय समय प्रारूप में बदलें।\",\n    \"formatDHMS\": \"दिन:घंटे:मिनट:सेकंड\",\n    \"formatHMS\": \"घंटे:मिनट:सेकंड\",\n    \"inputTitle\": \"इनपुट सेकंड\",\n    \"resultTitle\": \"समय\",\n    \"shortDescription\": \"सेकंड को समय प्रारूप में परिवर्तित करें\",\n    \"showZeroValues\": \"शून्य मान दिखाएं\",\n    \"timeFormat\": \"समय प्रारूप\",\n    \"timePadding\": \"समय पैडिंग\",\n    \"title\": \"सेकंड को समय में बदलें\",\n    \"toolInfo\": {\n      \"title\": \"{{title}} क्या है?\"\n    }\n  },\n  \"convertTimeToSeconds\": {\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"description\": \"समय प्रारूप को सेकंड की संख्या में बदलें।\",\n    \"formatDHMS\": \"दिन:घंटे:मिनट:सेकंड\",\n    \"formatHMS\": \"घंटे:मिनट:सेकंड\",\n    \"inputPlaceholder\": \"जैसे 1:30:45 या 1d 2h 30m 45s\",\n    \"inputTitle\": \"इनपुट समय\",\n    \"resultTitle\": \"सेकंड\",\n    \"shortDescription\": \"समय प्रारूप को सेकंड में परिवर्तित करें\",\n    \"timeFormat\": \"समय प्रारूप\",\n    \"title\": \"समय को सेकंड में बदलें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको फ़ॉर्मेट किए गए समय स्ट्रिंग (HH:MM:SS) को सेकंड में बदलने की सुविधा देता है। यह अवधि और समय अंतराल की गणना के लिए उपयोगी है।\",\n      \"title\": \"समय को सेकंड में बदलें\"\n    }\n  },\n  \"convertUnixToDate\": {\n    \"addUtcLabel\": \"'UTC' प्रत्यय जोड़ें\",\n    \"addUtcLabelDescription\": \"परिवर्तित तिथि के बाद 'UTC' प्रदर्शित करें (केवल UTC मोड के लिए)\",\n    \"description\": \"यूनिक्स टाइमस्टैम्प को मानव-पठनीय दिनांक में परिवर्तित करें।\",\n    \"outputOptions\": \"आउटपुट विकल्प\",\n    \"shortDescription\": \"यूनिक्स टाइमस्टैम्प को दिनांक में परिवर्तित करें\",\n    \"title\": \"यूनिक्स को दिनांक में परिवर्तित करें\",\n    \"toolInfo\": {\n      \"description\": \"यह उपकरण यूनिक्स टाइमस्टैम्प (सेकंड में) को मानव-पठनीय दिनांक प्रारूप (जैसे, YYYY-MM-DD HH:MM:SS) में परिवर्तित करता है। यह स्थानीय और UTC दोनों आउटपुट का समर्थन करता है, जिससे यह लॉग, API, या यूनिक्स समय का उपयोग करने वाले सिस्टम से टाइमस्टैम्प की त्वरित व्याख्या करने के लिए उपयोगी हो जाता है।\",\n      \"title\": \"यूनिक्स को दिनांक में परिवर्तित करें\"\n    },\n    \"useLocalTime\": \"स्थानीय समय का उपयोग करें\",\n    \"useLocalTimeDescription\": \"परिवर्तित तिथि को UTC के बजाय अपने स्थानीय समय क्षेत्र में दिखाएँ\",\n    \"withLabel\": \"विकल्प\"\n  },\n  \"crontabGuru\": {\n    \"countPlaceholder\": \"संख्या\",\n    \"cronOptions\": \"Cron विकल्प\",\n    \"description\": \"Cron एक्सप्रेशन को मानव-पठनीय प्रारूप में बदलें।\",\n    \"expressionPlaceholder\": \"जैसे */5 * * * *\",\n    \"inputTitle\": \"इनपुट Cron एक्सप्रेशन\",\n    \"resultTitle\": \"मानव-पठनीय विवरण\",\n    \"runCount\": \"रन की संख्या\",\n    \"shortDescription\": \"क्रॉन एक्सप्रेशन उत्पन्न करें और समझें\",\n    \"showNextRuns\": \"अगले रन दिखाएं\",\n    \"title\": \"Crontab गुरु\"\n  },\n  \"timeBetweenDates\": {\n    \"dateOptions\": \"तिथि विकल्प\",\n    \"description\": \"दो तिथियों के बीच का समय अंतराल ज्ञात करें।\",\n    \"endDate\": \"अंतिम तिथि\",\n    \"endDatePlaceholder\": \"YYYY-MM-DD\",\n    \"endDateTime\": \"समाप्ति तिथि और समय\",\n    \"endTime\": \"अंतिम समय\",\n    \"endTimePlaceholder\": \"HH:MM:SS\",\n    \"endTimezone\": \"समाप्ति समय क्षेत्र\",\n    \"formatDays\": \"दिन\",\n    \"formatHours\": \"घंटे\",\n    \"formatMinutes\": \"मिनट\",\n    \"formatSeconds\": \"सेकंड\",\n    \"includeTime\": \"समय शामिल करें\",\n    \"inputTitle\": \"तिथि जोड़े\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"resultTitle\": \"समय अंतराल\",\n    \"shortDescription\": \"दो तिथियों के बीच का समय गणना करें\",\n    \"startDate\": \"शुरुआती तिथि\",\n    \"startDatePlaceholder\": \"YYYY-MM-DD\",\n    \"startDateTime\": \"प्रारंभ तिथि और समय\",\n    \"startTime\": \"शुरुआती समय\",\n    \"startTimePlaceholder\": \"HH:MM:SS\",\n    \"startTimezone\": \"प्रारंभ समय क्षेत्र\",\n    \"title\": \"तिथियों के बीच समय\",\n    \"toolInfo\": {\n      \"description\": \"विभिन्न समय क्षेत्रों के समर्थन के साथ, दो तिथियों और समयों के बीच सटीक समय अंतर की गणना करें। यह टूल विभिन्न इकाइयों (वर्ष, महीने, दिन, घंटे, मिनट और सेकंड) में समय अंतर का विस्तृत विवरण प्रदान करता है।\",\n      \"title\": \"तिथियों के बीच का समय कैलकुलेटर\"\n    }\n  },\n  \"truncateClockTime\": {\n    \"description\": \"समय को निर्दिष्ट स्तर तक काटें।\",\n    \"format12Hour\": \"12 घंटे प्रारूप\",\n    \"format24Hour\": \"24 घंटे प्रारूप\",\n    \"inputTitle\": \"इनपुट समय\",\n    \"printDroppedComponents\": \"गिरे हुए घटकों को प्रिंट करें\",\n    \"resultTitle\": \"काटा गया समय\",\n    \"roundDown\": \"नीचे गोल करें\",\n    \"roundUp\": \"ऊपर गोल करें\",\n    \"shortDescription\": \"घड़ी के समय को निर्दिष्ट परिशुद्धता तक छोटा करें\",\n    \"timeFormat\": \"समय प्रारूप\",\n    \"timePadding\": \"समय पैडिंग\",\n    \"title\": \"घड़ी का समय काटें\",\n    \"toolInfo\": {\n      \"title\": \"{{title}} क्या है?\"\n    },\n    \"truncateMinutesAndSeconds\": \"मिनट और सेकंड को छोटा करें\",\n    \"truncateMinutesAndSecondsDescription\": \"प्रत्येक घड़ी के समय से मिनट और सेकंड दोनों घटकों को हटा दें।\",\n    \"truncateOnlySeconds\": \"केवल सेकंड काटें\",\n    \"truncateOnlySecondsDescription\": \"प्रत्येक घड़ी के समय से सेकंड घटक हटा दें।\",\n    \"truncateTo\": \"काटने का स्तर\",\n    \"truncateToHours\": \"घंटे\",\n    \"truncateToMinutes\": \"मिनट\",\n    \"truncateToSeconds\": \"सेकंड\",\n    \"truncationOptions\": \"काटने के विकल्प\",\n    \"truncationSide\": \"काटने की तरफ\",\n    \"useZeroPadding\": \"शून्य पैडिंग का उपयोग करें\",\n    \"zeroPaddingDescription\": \"सभी समय घटकों को हमेशा दो अंक चौड़ा रखें।\",\n    \"zeroPrintDescription\": \"गिराए गए भागों को शून्य मान \\\"00\\\" के रूप में प्रदर्शित करें।\",\n    \"zeroPrintTruncatedParts\": \"शून्य-मुद्रित काटे गए भाग\"\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/translation.json",
    "content": "{\n  \"audio\": {\n    \"changeSpeed\": {\n      \"description\": \"ऑडियो फ़ाइलों की प्लेबैक गति बदलें। पिच बनाए रखते हुए ऑडियो को तेज़ या धीमा करें।\",\n      \"name\": \"ऑडियो गति बदलें\",\n      \"shortDescription\": \"ऑडियो फ़ाइलों की गति बदलें\"\n    },\n    \"extractAudio\": {\n      \"description\": \"वीडियो फ़ाइल से ऑडियो ट्रैक निकालें और इसे अपने चुने हुए प्रारूप (एएसी, एमपी3, डब्ल्यूएवी) में एक अलग ऑडियो फ़ाइल के रूप में सहेजें।\",\n      \"name\": \"ऑडियो निकालें\",\n      \"shortDescription\": \"वीडियो फ़ाइलों (एमपी4, एमओवी, आदि) से ऑडियो निकालें एएसी, एमपी3, या डब्ल्यूएवी में।\"\n    }\n  },\n  \"baseFileInput\": {\n    \"copyFailed\": \"कॉपी करने में विफल: {{error}}\",\n    \"dropFileHere\": \"यहाँ अपनी {{type}} डालें\",\n    \"fileCopied\": \"फ़ाइल कॉपी की गई\",\n    \"selectFileDescription\": \"यहाँ क्लिक करें अपने डिवाइस से {{type}} चुनने के लिए, Ctrl+V दबाएं क्लिपबोर्ड से {{type}} का उपयोग करने के लिए, या डेस्कटॉप से फ़ाइल को खींचकर डालें\"\n  },\n  \"categories\": {\n    \"audio\": {\n      \"description\": \"ऑडियो के साथ काम करने के लिए टूल्स – वीडियो से ऑडियो निकालें, ऑडियो गति समायोजित करें, कई ऑडियो फ़ाइलों को मर्ज करें और बहुत कुछ।\",\n      \"title\": \"ऑडियो टूल्स\"\n    },\n    \"csv\": {\n      \"description\": \"CSV फ़ाइलों के साथ काम करने के लिए टूल्स - CSV को विभिन्न प्रारूपों में बदलें, CSV डेटा में हेरफेर करें, CSV संरचना को मान्य करें, और CSV फ़ाइलों को कुशलतापूर्वक संसाधित करें।\",\n      \"title\": \"CSV टूल्स\"\n    },\n    \"gif\": {\n      \"description\": \"GIF एनिमेशन के साथ काम करने के लिए टूल्स – पारदर्शी GIF बनाएं, GIF फ्रेम निकालें, GIF में टेक्स्ट जोड़ें, क्रॉप, घुमाएं, GIF को उलटा करें, और बहुत कुछ।\",\n      \"title\": \"GIF टूल्स\"\n    },\n    \"image-generic\": {\n      \"description\": \"चित्रों के साथ काम करने के लिए टूल्स – संपीड़ित करें, आकार बदलें, क्रॉप करें, JPG में बदलें, घुमाएं, पृष्ठभूमि हटाएं और बहुत कुछ।\",\n      \"title\": \"छवि टूल्स\"\n    },\n    \"json\": {\n      \"description\": \"JSON डेटा संरचनाओं के साथ काम करने के लिए टूल्स – JSON ऑब्जेक्ट को सुंदर और संक्षिप्त करें, JSON सरणियों को समतल करें, JSON मूल्यों को स्ट्रिंगिफाई करें, डेटा का विश्लेषण करें, और बहुत कुछ\",\n      \"title\": \"JSON टूल्स\"\n    },\n    \"list\": {\n      \"description\": \"सूचियों के साथ काम करने के लिए टूल्स – क्रमबद्ध करें, उलटा करें, सूचियों को यादृच्छिक करें, अद्वितीय और डुप्लिकेट सूची आइटम खोजें, सूची आइटम विभाजक बदलें, और बहुत कुछ।\",\n      \"title\": \"सूची टूल्स\"\n    },\n    \"number\": {\n      \"description\": \"संख्याओं के साथ काम करने के लिए टूल्स – संख्या अनुक्रम उत्पन्न करें, संख्याओं को शब्दों में और शब्दों को संख्याओं में बदलें, क्रमबद्ध करें, गोल करें, संख्याओं का गुणनखंड करें, और बहुत कुछ।\",\n      \"title\": \"संख्या टूल्स\"\n    },\n    \"pdf\": {\n      \"description\": \"PDF फ़ाइलों के साथ काम करने के लिए टूल्स - PDF से टेक्स्ट निकालें, PDF को अन्य प्रारूपों में बदलें, PDF में हेरफेर करें, और बहुत कुछ।\",\n      \"title\": \"PDF टूल्स\"\n    },\n    \"png\": {\n      \"description\": \"PNG छवियों के साथ काम करने के लिए टूल्स – PNG को JPG में बदलें, पारदर्शी PNG बनाएं, PNG रंग बदलें, क्रॉप, घुमाएं, PNG का आकार बदलें, और बहुत कुछ।\",\n      \"title\": \"PNG टूल्स\"\n    },\n    \"seeAll\": \"सभी {{title}} देखें\",\n    \"string\": {\n      \"description\": \"टेक्स्ट के साथ काम करने के लिए टूल्स – टेक्स्ट को छवियों में बदलें, टेक्स्ट खोजें और बदलें, टेक्स्ट को टुकड़ों में विभाजित करें, टेक्स्ट पंक्तियों को जोड़ें, टेक्स्ट दोहराएं, और बहुत कुछ।\",\n      \"title\": \"टेक्स्ट टूल्स\"\n    },\n    \"time\": {\n      \"description\": \"समय और तिथि के साथ काम करने के लिए टूल्स – समय अंतर की गणना करें, समय क्षेत्रों के बीच बदलें, तिथियों को फॉर्मेट करें, तिथि अनुक्रम उत्पन्न करें, और बहुत कुछ।\",\n      \"title\": \"समय टूल्स\"\n    },\n    \"try\": \"{{title}} आज़माएं\",\n    \"video\": {\n      \"description\": \"वीडियो के साथ काम करने के लिए टूल्स – वीडियो से फ्रेम निकालें, वीडियो से GIF बनाएं, वीडियो को विभिन्न प्रारूपों में बदलें, और बहुत कुछ।\",\n      \"title\": \"वीडियो टूल्स\"\n    },\n    \"xml\": {\n      \"description\": \"XML डेटा संरचनाओं के साथ काम करने के लिए टूल्स - व्यूअर, ब्यूटिफायर, वैलिडेटर और बहुत कुछ\",\n      \"title\": \"XML टूल्स\"\n    }\n  },\n  \"csv\": {\n    \"findIncompleteCsvRecords\": {\n      \"description\": \"बस नीचे फॉर्म में अपनी सीएसवी फ़ाइल अपलोड करें और यह टूल स्वचालित रूप से जांच करेगा कि क्या कोई पंक्ति या स्तंभ मूल्य नहीं खो रहे हैं। टूल विकल्पों में, आप इनपुट फ़ाइल प्रारूप को समायोजित कर सकते हैं (विभाजक, उद्धरण वर्ण, और टिप्पणी वर्ण निर्दिष्ट करें)। इसके अतिरिक्त, आप खाली मूल्यों की जांच सक्षम कर सकते हैं, खाली पंक्तियों को छोड़ सकते हैं, और आउटपुट में त्रुटि संदेशों की संख्या पर सीमा निर्धारित कर सकते हैं।\",\n      \"name\": \"अधूरे सीएसवी रिकॉर्ड खोजें\",\n      \"shortDescription\": \"सीएसवी में जल्दी से पंक्तियां और स्तंभ खोजें जो मूल्य खो रहे हैं।\"\n    }\n  },\n  \"hero\": {\n    \"brand\": \"ओमनीटूल्स\",\n    \"description\": \"ओमनीटूल्स के साथ अपनी उत्पादकता बढ़ाएं, जल्दी काम करने के लिए अंतिम टूलकिट! छवियों, टेक्स्ट, सूचियों और डेटा को संपादित करने के लिए हजारों उपयोगकर्ता-अनुकूल उपयोगिताओं तक पहुंचें, सभी सीधे अपने ब्राउज़र से।\",\n    \"examples\": {\n      \"calculateNumberSum\": \"संख्याओं का योग करें\",\n      \"changeGifSpeed\": \"GIF गति बदलें\",\n      \"compressPng\": \"PNG संपीड़ित करें\",\n      \"createTransparentImage\": \"पारदर्शी छवि बनाएं\",\n      \"prettifyJson\": \"JSON सुंदर बनाएं\",\n      \"sortList\": \"सूची क्रमबद्ध करें\",\n      \"splitPdf\": \"PDF विभाजित करें\",\n      \"splitText\": \"टेक्स्ट विभाजित करें\",\n      \"trimVideo\": \"वीडियो ट्रिम करें\"\n    },\n    \"searchPlaceholder\": \"सभी टूल्स खोजें\",\n    \"title\": \"के साथ जल्दी काम करें\"\n  },\n  \"inputFooter\": {\n    \"clear\": \"साफ़ करें\",\n    \"copyToClipboard\": \"क्लिपबोर्ड पर कॉपी करें\",\n    \"importFromFile\": \"फ़ाइल से आयात करें\"\n  },\n  \"list\": {\n    \"group\": {\n      \"description\": \"सूची आइटम को समूहित करने के लिए दुनिया का सबसे सरल ब्राउज़र-आधारित उपयोगिता। अपनी सूची इनपुट करें और समूहीकरण मानदंड निर्दिष्ट करें ताकि आइटम को तार्किक समूहों में व्यवस्थित किया जा सके। डेटा को वर्गीकृत करने, जानकारी को व्यवस्थित करने, या संरचित सूचियां बनाने के लिए बिल्कुल सही। कस्टम विभाजक और विभिन्न समूहीकरण विकल्पों का समर्थन करता है।\",\n      \"name\": \"समूह\",\n      \"shortDescription\": \"सामान्य गुणों द्वारा सूची आइटम को समूहित करें\"\n    },\n    \"reverse\": {\n      \"description\": \"यह एक सुपर सरल ब्राउज़र-आधारित एप्लिकेशन है जो सभी सूची आइटम को उल्टे क्रम में प्रिंट करती है। इनपुट आइटम किसी भी प्रतीक से अलग किए जा सकते हैं और आप उलटे सूची आइटम के विभाजक को भी बदल सकते हैं।\",\n      \"name\": \"उलटा\",\n      \"shortDescription\": \"जल्दी से सूची को उलटा करें\"\n    },\n    \"sort\": {\n      \"description\": \"यह एक सुपर सरल ब्राउज़र-आधारित एप्लिकेशन है जो सूची में आइटम को क्रमबद्ध करती है और उन्हें बढ़ते या घटते क्रम में व्यवस्थित करती है। आप आइटम को वर्णानुक्रमिक, संख्यात्मक, या उनकी लंबाई के अनुसार क्रमबद्ध कर सकते हैं। आप डुप्लिकेट और खाली आइटम को भी हटा सकते हैं, साथ ही उन आइटम को ट्रिम कर सकते हैं जिनके चारों ओर सफेद स्थान है। आप इनपुट सूची आइटम को अलग करने के लिए कोई भी विभाजक वर्ण उपयोग कर सकते हैं या वैकल्पिक रूप से उन्हें अलग करने के लिए एक नियमित अभिव्यक्ति का उपयोग कर सकते हैं। इसके अतिरिक्त, आप क्रमबद्ध आउटपुट सूची के लिए एक नया डिलिमिटर बना सकते हैं।\",\n      \"name\": \"क्रमबद्ध\",\n      \"shortDescription\": \"जल्दी से सूची को क्रमबद्ध करें\"\n    }\n  },\n  \"navbar\": {\n    \"buyMeACoffee\": \"मुझे कॉफी खरीदें\",\n    \"hireMe\": \"मुझे नौकरी दें\",\n    \"home\": \"होम\",\n    \"tools\": \"टूल्स\"\n  },\n  \"number\": {\n    \"generate\": {\n      \"description\": \"अपने ब्राउज़र में पूर्णांकों की सूची की तुरंत गणना करें। अपनी सूची प्राप्त करने के लिए, बस पहला पूर्णांक निर्दिष्ट करें, नीचे विकल्पों में मान और कुल संख्या बदलें, और यह उपयोगिता उतने पूर्णांक उत्पन्न करेगी\",\n      \"name\": \"संख्याएं उत्पन्न करें\",\n      \"shortDescription\": \"अपने ब्राउज़र में पूर्णांकों की सूची की तुरंत गणना करें\"\n    },\n    \"sum\": {\n      \"description\": \"यह एक सुपर सरल ब्राउज़र-आधारित एप्लिकेशन है जो संख्याओं को जोड़ती है। इनपुट संख्याएं किसी भी प्रतीक से अलग की जा सकती हैं और आप जोड़ी गई संख्याओं के विभाजक को भी बदल सकते हैं।\",\n      \"name\": \"संख्याएं जोड़ें\",\n      \"shortDescription\": \"जल्दी से संख्याओं की सूची जोड़ें\"\n    }\n  },\n  \"numericInputWithUnit\": {\n    \"unit\": \"इकाई\"\n  },\n  \"pdf\": {\n    \"compressPdf\": {\n      \"description\": \"गोस्टस्क्रिप्ट का उपयोग करके गुणवत्ता बनाए रखते हुए पीडीएफ फ़ाइल आकार कम करें\",\n      \"name\": \"पीडीएफ संपीड़ित करें\",\n      \"shortDescription\": \"अपने ब्राउज़र में सुरक्षित रूप से पीडीएफ फ़ाइलों को संपीड़ित करें\"\n    },\n    \"mergePdf\": {\n      \"description\": \"कई पीडीएफ फ़ाइलों को एक दस्तावेज़ में जोड़ें।\",\n      \"name\": \"पीडीएफ मर्ज करें\",\n      \"shortDescription\": \"कई पीडीएफ फ़ाइलों को एक दस्तावेज़ में मर्ज करें\"\n    },\n    \"pdfToEpub\": {\n      \"description\": \"बेहतर ई-रीडर संगतता के लिए पीडीएफ दस्तावेज़ों को ईपीयूबी फ़ाइलों में बदलें।\",\n      \"name\": \"पीडीएफ से ईपीयूबी\",\n      \"shortDescription\": \"पीडीएफ फ़ाइलों को ईपीयूबी प्रारूप में बदलें\"\n    },\n    \"protectPdf\": {\n      \"description\": \"अपने ब्राउज़र में सुरक्षित रूप से अपनी पीडीएफ फ़ाइलों में पासवर्ड सुरक्षा जोड़ें\",\n      \"name\": \"पीडीएफ सुरक्षित करें\",\n      \"shortDescription\": \"पीडीएफ फ़ाइलों को सुरक्षित रूप से पासवर्ड सुरक्षित करें\"\n    },\n    \"splitPdf\": {\n      \"description\": \"पेज नंबर या श्रेणियों का उपयोग करके पीडीएफ फ़ाइल से विशिष्ट पेज निकालें (उदाहरण के लिए, 1,5-8)\",\n      \"name\": \"पीडीएफ विभाजित करें\",\n      \"shortDescription\": \"पीडीएफ फ़ाइल से विशिष्ट पेज निकालें\"\n    }\n  },\n  \"resultFooter\": {\n    \"copy\": \"क्लिपबोर्ड पर कॉपी करें\",\n    \"download\": \"डाउनलोड\"\n  },\n  \"string\": {\n    \"createPalindrome\": {\n      \"description\": \"किसी भी टेक्स्ट से पैलिंड्रोम बनाने के लिए दुनिया का सबसे सरल ब्राउज़र-आधारित उपयोगिता। टेक्स्ट इनपुट करें और इसे तुरंत एक पैलिंड्रोम में बदल दें जो आगे और पीछे एक जैसा पढ़ता है। शब्द खेलों, सममित टेक्स्ट पैटर्न बनाने, या भाषाई जिज्ञासाओं की खोज के लिए बिल्कुल सही।\",\n      \"name\": \"पैलिंड्रोम बनाएं\",\n      \"shortDescription\": \"ऐसा टेक्स्ट बनाएं जो आगे और पीछे एक जैसा पढ़ता है\"\n    },\n    \"palindrome\": {\n      \"description\": \"यह जांचने के लिए दुनिया का सबसे सरल ब्राउज़र-आधारित उपयोगिता कि टेक्स्ट पैलिंड्रोम है या नहीं। तुरंत सत्यापित करें कि क्या आपका टेक्स्ट आगे और पीछे एक जैसा पढ़ता है। शब्द पहेलियों, भाषाई विश्लेषण, या सममित टेक्स्ट पैटर्न को मान्य करने के लिए बिल्कुल सही। विभिन्न विभाजकों और बहु-शब्द पैलिंड्रोम पहचान का समर्थन करता है।\",\n      \"name\": \"पैलिंड्रोम\",\n      \"shortDescription\": \"जांचें कि क्या टेक्स्ट आगे और पीछे एक जैसा पढ़ता है\"\n    },\n    \"repeat\": {\n      \"description\": \"यह टूल आपको वैकल्पिक विभाजक के साथ दिए गए टेक्स्ट को कई बार दोहराने की अनुमति देता है।\",\n      \"name\": \"टेक्स्ट दोहराएं\",\n      \"shortDescription\": \"टेक्स्ट को कई बार दोहराएं\"\n    },\n    \"reverse\": {\n      \"description\": \"टेक्स्ट को उलटने के लिए दुनिया का सबसे सरल ब्राउज़र-आधारित उपयोगिता। कोई भी टेक्स्ट इनपुट करें और इसे तुरंत उलटा प्राप्त करें, वर्ण दर वर्ण। दर्पण टेक्स्ट बनाने, पैलिंड्रोम का विश्लेषण करने, या टेक्स्ट पैटर्न के साथ खेलने के लिए बिल्कुल सही। उलटते समय स्थान और विशेष वर्णों को संरक्षित करता है।\",\n      \"name\": \"उलटा\",\n      \"shortDescription\": \"किसी भी टेक्स्ट को वर्ण दर वर्ण उलटा करें\"\n    },\n    \"toMorse\": {\n      \"description\": \"टेक्स्ट को मोर्स कोड में बदलने के लिए दुनिया का सबसे सरल ब्राउज़र-आधारित उपयोगिता। बाईं ओर इनपुट फॉर्म में अपना टेक्स्ट लोड करें और आपको तुरंत आउटपुट क्षेत्र में मोर्स कोड मिलेगा। शक्तिशाली, मुफ्त, और तेज़। टेक्स्ट लोड करें – मोर्स कोड प्राप्त करें।\",\n      \"name\": \"टेक्स्ट से मोर्स\",\n      \"shortDescription\": \"टेक्स्ट को जल्दी से मोर्स में एनकोड करें\"\n    },\n    \"uppercase\": {\n      \"description\": \"टेक्स्ट को बड़े अक्षरों में बदलने के लिए दुनिया का सबसे सरल ब्राउज़र-आधारित उपयोगिता। बस अपना टेक्स्ट इनपुट करें और यह स्वचालित रूप से सभी बड़े अक्षरों में परिवर्तित हो जाएगा। शीर्षक बनाने, टेक्स्ट पर जोर देने, या टेक्स्ट प्रारूप को मानकीकृत करने के लिए बिल्कुल सही। विभिन्न टेक्स्ट प्रारूपों का समर्थन करता है और विशेष वर्णों को संरक्षित करता है।\",\n      \"name\": \"बड़े अक्षर\",\n      \"shortDescription\": \"टेक्स्ट को बड़े अक्षरों में बदलें\"\n    }\n  },\n  \"toolExamples\": {\n    \"subtitle\": \"आज़माने के लिए क्लिक करें!\",\n    \"title\": \"{{title}} उदाहरण\"\n  },\n  \"toolFileResult\": {\n    \"copied\": \"फ़ाइल कॉपी की गई\",\n    \"copyFailed\": \"कॉपी करने में विफल: {{error}}\",\n    \"loading\": \"लोड हो रहा है... इसमें कुछ समय लग सकता है।\",\n    \"result\": \"परिणाम\"\n  },\n  \"toolHeader\": {\n    \"seeExamples\": \"उदाहरण देखें\"\n  },\n  \"toolLayout\": {\n    \"allToolsTitle\": \"सभी {{type}}\"\n  },\n  \"toolMultiFileResult\": {\n    \"copied\": \"फ़ाइल कॉपी की गई\",\n    \"copyFailed\": \"कॉपी करने में विफल: {{error}}\",\n    \"loading\": \"लोड हो रहा है... इसमें कुछ समय लग सकता है।\",\n    \"result\": \"परिणाम\"\n  },\n  \"toolMultipleAudioInput\": {\n    \"inputTitle\": \"इनपुट {{type}}\",\n    \"noFilesSelected\": \"कोई फ़ाइल चयनित नहीं\"\n  },\n  \"toolMultiplePdfInput\": {\n    \"inputTitle\": \"इनपुट {{type}}\",\n    \"noFilesSelected\": \"कोई फ़ाइल चयनित नहीं\"\n  },\n  \"toolOptions\": {\n    \"title\": \"टूल विकल्प\"\n  },\n  \"toolTextInput\": {\n    \"copied\": \"टेक्स्ट कॉपी किया गया\",\n    \"copyFailed\": \"कॉपी करने में विफल: {{error}}\",\n    \"input\": \"इनपुट टेक्स्ट\",\n    \"placeholder\": \"यहाँ अपना टेक्स्ट दर्ज करें...\"\n  },\n  \"toolTextResult\": {\n    \"copied\": \"टेक्स्ट कॉपी किया गया\",\n    \"copyFailed\": \"कॉपी करने में विफल: {{error}}\",\n    \"loading\": \"लोड हो रहा है... इसमें कुछ समय लग सकता है।\",\n    \"result\": \"परिणाम\"\n  },\n  \"userTypes\": {\n    \"developers\": \"डेवलपर्स\",\n    \"generalUsers\": \"सामान्य उपयोगकर्ता\"\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/video.json",
    "content": "{\n  \"changeSpeed\": {\n    \"defaultMultiplier\": \"डिफ़ॉल्ट गुणक: 2 का अर्थ है 2x तेज़\",\n    \"description\": \"वीडियो फ़ाइलों की प्लेबैक गति बदलें।\",\n    \"factorPlaceholder\": \"कारक (जैसे 0.5, 1.5, 2.0)\",\n    \"formatAvi\": \"AVI\",\n    \"formatMov\": \"MOV\",\n    \"formatMp4\": \"MP4\",\n    \"inputTitle\": \"इनपुट वीडियो\",\n    \"newVideoSpeed\": \"नई वीडियो स्पीड\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"preserveAudio\": \"ऑडियो संरक्षित करें\",\n    \"resultTitle\": \"गति बदली गई वीडियो\",\n    \"settingSpeed\": \"गति सेट करना\",\n    \"shortDescription\": \"वीडियो प्लेबैक गति बदलें\",\n    \"speedFactor\": \"गति कारक\",\n    \"speedOptions\": \"गति विकल्प\",\n    \"title\": \"वीडियो गति बदलें\",\n    \"toolInfo\": {\n      \"title\": \"{{title}} क्या है?\"\n    }\n  },\n  \"compress\": {\n    \"compressionOptions\": \"संपीड़न विकल्प\",\n    \"default\": \"गलती करना\",\n    \"description\": \"वीडियो फ़ाइल आकार कम करें।\",\n    \"formatAvi\": \"AVI\",\n    \"formatMp4\": \"MP4\",\n    \"inputTitle\": \"इनपुट वीडियो\",\n    \"loadingText\": \"वीडियो संपीड़ित किया जा रहा है...\",\n    \"lossless\": \"दोषरहित\",\n    \"outputFormat\": \"आउटपुट प्रारूप\",\n    \"quality\": \"गुणवत्ता (सीआरएफ)\",\n    \"qualityHigh\": \"उच्च\",\n    \"qualityLow\": \"कम\",\n    \"qualityMedium\": \"मध्यम\",\n    \"resolution\": \"रिज़ॉल्यूशन\",\n    \"resolution360p\": \"360p\",\n    \"resolution480p\": \"480p\",\n    \"resolution720p\": \"720p\",\n    \"resolutionOriginal\": \"मूल\",\n    \"resultTitle\": \"संपीड़ित वीडियो\",\n    \"shortDescription\": \"विभिन्न रिज़ॉल्यूशन पर स्केल करके वीडियो संपीड़ित करें\",\n    \"title\": \"वीडियो संपीड़ित करें\",\n    \"videoQuality\": \"वीडियो गुणवत्ता\",\n    \"worst\": \"बहुत बुरा\"\n  },\n  \"cropVideo\": {\n    \"aspectRatio\": \"आकार अनुपात\",\n    \"cropArea\": \"क्रॉप क्षेत्र\",\n    \"cropCoordinates\": \"फसल निर्देशांक\",\n    \"cropMethod\": \"क्रॉप विधि\",\n    \"cropOptions\": \"क्रॉप विकल्प\",\n    \"croppingVideo\": \"वीडियो क्रॉप करना\",\n    \"description\": \"वीडियो से अनावश्यक भाग हटाएं।\",\n    \"errorBeyondHeight\": \"फसल क्षेत्र वीडियो की ऊंचाई से आगे तक फैला हुआ है ({{height}}पिक्सल)\",\n    \"errorBeyondWidth\": \"फसल क्षेत्र वीडियो की चौड़ाई से आगे तक फैला हुआ है ({{width}}पिक्सल)\",\n    \"errorCroppingVideo\": \"वीडियो क्रॉप करने में त्रुटि हुई। कृपया पैरामीटर और वीडियो फ़ाइल जाँचें।\",\n    \"errorLoadingDimensions\": \"वीडियो आयाम लोड करने में विफल\",\n    \"errorNonNegativeCoordinates\": \"X और Y निर्देशांक गैर-ऋणात्मक होने चाहिए\",\n    \"errorPositiveDimensions\": \"चौड़ाई और ऊंचाई धनात्मक होनी चाहिए\",\n    \"height\": \"ऊंचाई\",\n    \"heightPlaceholder\": \"पिक्सेल\",\n    \"inputTitle\": \"इनपुट वीडियो\",\n    \"loadVideoForDimensions\": \"आयाम देखने के लिए वीडियो लोड करें\",\n    \"longDescription\": \"यह टूल आपको वीडियो फ़ाइलों को क्रॉप करके अवांछित क्षेत्रों को हटाने या वीडियो के विशिष्ट भागों पर ध्यान केंद्रित करने की अनुमति देता है। काली पट्टियों को हटाने, आस्पेक्ट रेशियो को समायोजित करने या महत्वपूर्ण सामग्री पर ध्यान केंद्रित करने के लिए उपयोगी। MP4, MOV और AVI सहित विभिन्न वीडियो फ़ॉर्मेट का समर्थन करता है।\",\n    \"methodAspectRatio\": \"आकार अनुपात\",\n    \"methodManual\": \"मैनुअल\",\n    \"ratio16x9\": \"16:9\",\n    \"ratio1x1\": \"1:1\",\n    \"ratio4x3\": \"4:3\",\n    \"resultTitle\": \"क्रॉप किया गया वीडियो\",\n    \"shortDescription\": \"अवांछित क्षेत्रों को हटाने के लिए वीडियो को क्रॉप करें\",\n    \"title\": \"वीडियो क्रॉप करें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको वीडियो फ़ाइलों को क्रॉप करके अवांछित क्षेत्रों को हटाने की सुविधा देता है। आप X, Y निर्देशांक और चौड़ाई, ऊँचाई के आयाम निर्धारित करके क्रॉप क्षेत्र निर्दिष्ट कर सकते हैं।\",\n      \"title\": \"वीडियो क्रॉप करें\"\n    },\n    \"videoDimensions\": \"वीडियो आयाम: {{width}} × {{height}} पिक्सेल\",\n    \"videoInformation\": \"वीडियो जानकारी\",\n    \"width\": \"चौड़ाई\",\n    \"widthPlaceholder\": \"पिक्सेल\",\n    \"xCoordinate\": \"X (बाएं)\",\n    \"xPlaceholder\": \"पिक्सेल\",\n    \"xPosition\": \"X स्थिति\",\n    \"yCoordinate\": \"Y (ऊपर)\",\n    \"yPlaceholder\": \"पिक्सेल\",\n    \"yPosition\": \"Y स्थिति\"\n  },\n  \"flip\": {\n    \"description\": \"वीडियो को क्षैतिज या लंबवत रूप से फ्लिप करें।\",\n    \"directionBoth\": \"दोनों\",\n    \"directionHorizontal\": \"क्षैतिज\",\n    \"directionVertical\": \"लंबवत\",\n    \"flipDirection\": \"फ्लिप दिशा\",\n    \"flipOptions\": \"फ्लिप विकल्प\",\n    \"flippingVideo\": \"फ़्लिपिंग वीडियो\",\n    \"horizontalLabel\": \"क्षैतिज (दर्पण)\",\n    \"inputTitle\": \"इनपुट वीडियो\",\n    \"orientation\": \"अभिविन्यास\",\n    \"preserveAudio\": \"ऑडियो संरक्षित करें\",\n    \"resultTitle\": \"फ्लिप किया गया वीडियो\",\n    \"shortDescription\": \"वीडियो को क्षैतिज या लंबवत रूप से फ़्लिप करें\",\n    \"title\": \"वीडियो फ्लिप करें\",\n    \"verticalLabel\": \"ऊर्ध्वाधर (उल्टा)\"\n  },\n  \"gif\": {\n    \"changeSpeed\": {\n      \"delayPlaceholder\": \"मिलीसेकंड\",\n      \"description\": \"GIF एनिमेटेड फ़ाइलों की गति बदलें।\",\n      \"factorPlaceholder\": \"कारक (जैसे 0.5, 1.5, 2.0)\",\n      \"frameDelay\": \"फ्रेम विलंब\",\n      \"inputTitle\": \"इनपुट GIF\",\n      \"resultTitle\": \"गति बदली गई GIF\",\n      \"shortDescription\": \"GIF एनीमेशन की गति बदलें\",\n      \"speedFactor\": \"गति कारक\",\n      \"speedOptions\": \"गति विकल्प\",\n      \"title\": \"GIF गति बदलें\"\n    }\n  },\n  \"loop\": {\n    \"countPlaceholder\": \"संख्या\",\n    \"description\": \"वीडियो को लूप में चलाएं।\",\n    \"durationPlaceholder\": \"सेकंड\",\n    \"fadeDuration\": \"फेड अवधि\",\n    \"fadeTransition\": \"फेड संक्रमण\",\n    \"inputTitle\": \"इनपुट वीडियो\",\n    \"loopCount\": \"लूप की संख्या\",\n    \"loopInfinitely\": \"अनंत लूप\",\n    \"loopOptions\": \"लूप विकल्प\",\n    \"loopingVideo\": \"पाशन वीडियो\",\n    \"loops\": \"छोरों\",\n    \"numberOfLoops\": \"लूपों की संख्या\",\n    \"resultTitle\": \"लूप वीडियो\",\n    \"shortDescription\": \"लूपिंग वीडियो फ़ाइलें बनाएँ\",\n    \"title\": \"वीडियो लूप करें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको मूल वीडियो को कई बार दोहराकर एक लूपिंग वीडियो बनाने की सुविधा देता है। आप यह भी निर्दिष्ट कर सकते हैं कि वीडियो को कितनी बार लूप किया जाना चाहिए।\",\n      \"title\": \"{{title}} क्या है?\"\n    }\n  },\n  \"mergeVideo\": {\n    \"description\": \"एकाधिक वीडियो फ़ाइलों को एक सतत वीडियो में संयोजित करें।\",\n    \"longDescription\": \"यह टूल आपको कई वीडियो फ़ाइलों को एक ही वीडियो में मर्ज या जोड़ने की सुविधा देता है। बस अपनी वीडियो फ़ाइलें अपलोड करें, उन्हें मनचाहे क्रम में व्यवस्थित करें, और आसानी से शेयर या एडिट करने के लिए उन्हें एक फ़ाइल में मर्ज करें।\",\n    \"shortDescription\": \"वीडियो को आसानी से जोड़ें और मर्ज करें।\",\n    \"title\": \"वीडियो मर्ज करें\"\n  },\n  \"rotate\": {\n    \"180Degrees\": \"180° (उल्टा)\",\n    \"270Degrees\": \"270° (90° वामावर्त)\",\n    \"90Degrees\": \"90° दक्षिणावर्त\",\n    \"angle180\": \"180 डिग्री\",\n    \"angle270\": \"270 डिग्री\",\n    \"angle90\": \"90 डिग्री\",\n    \"customAngle\": \"कस्टम कोण\",\n    \"customAnglePlaceholder\": \"डिग्री\",\n    \"description\": \"वीडियो को निर्दिष्ट कोण से घुमाएं।\",\n    \"inputTitle\": \"इनपुट वीडियो\",\n    \"preserveAudio\": \"ऑडियो संरक्षित करें\",\n    \"resultTitle\": \"घुमाया गया वीडियो\",\n    \"rotatingVideo\": \"घूमता हुआ वीडियो\",\n    \"rotation\": \"ROTATION\",\n    \"rotationAngle\": \"घुमाने का कोण\",\n    \"rotationOptions\": \"घुमाने के विकल्प\",\n    \"shortDescription\": \"वीडियो को निर्दिष्ट डिग्री तक घुमाएँ\",\n    \"title\": \"वीडियो घुमाएं\"\n  },\n  \"trim\": {\n    \"description\": \"वीडियो से अनावश्यक भाग हटाएं।\",\n    \"endPlaceholder\": \"सेकंड\",\n    \"endTime\": \"अंतिम समय\",\n    \"inputTitle\": \"इनपुट वीडियो\",\n    \"preserveAudio\": \"ऑडियो संरक्षित करें\",\n    \"resultTitle\": \"ट्रिम किया गया वीडियो\",\n    \"shortDescription\": \"अवांछित अनुभागों को हटाकर वीडियो ट्रिम करें\",\n    \"startPlaceholder\": \"सेकंड\",\n    \"startTime\": \"शुरुआती समय\",\n    \"timestamps\": \"मुहर\",\n    \"title\": \"वीडियो ट्रिम करें\",\n    \"trimOptions\": \"ट्रिम विकल्प\"\n  },\n  \"videoToGif\": {\n    \"conversionOptions\": \"रूपांतरण विकल्प\",\n    \"description\": \"वीडियो को GIF एनिमेटेड फ़ाइल में बदलें।\",\n    \"frameRate\": \"फ्रेम दर\",\n    \"frameRatePlaceholder\": \"FPS\",\n    \"height\": \"ऊंचाई\",\n    \"heightPlaceholder\": \"पिक्सेल\",\n    \"inputTitle\": \"इनपुट वीडियो\",\n    \"quality\": \"गुणवत्ता\",\n    \"qualityHigh\": \"उच्च\",\n    \"qualityLow\": \"कम\",\n    \"qualityMedium\": \"मध्यम\",\n    \"resize\": \"आकार बदलें\",\n    \"resultTitle\": \"GIF फ़ाइल\",\n    \"shortDescription\": \"वीडियो को एनिमेटेड GIF में बदलें\",\n    \"title\": \"वीडियो से GIF\",\n    \"width\": \"चौड़ाई\",\n    \"widthPlaceholder\": \"पिक्सेल\"\n  }\n}\n"
  },
  {
    "path": "public/locales/hi/xml.json",
    "content": "{\n  \"xmlBeautifier\": {\n    \"description\": \"XML को सुंदर प्रारूप में बदलें।\",\n    \"formattingOptions\": \"फॉर्मेटिंग विकल्प\",\n    \"indentCharacter\": \"इंडेंट वर्ण\",\n    \"indentSize\": \"इंडेंट आकार\",\n    \"indentation\": \"खरोज\",\n    \"inputTitle\": \"इनपुट XML\",\n    \"preserveWhitespace\": \"सफेद स्थान संरक्षित करें\",\n    \"removeComments\": \"टिप्पणियां हटाएं\",\n    \"resultTitle\": \"सुंदर XML\",\n    \"shortDescription\": \"XML कोड को प्रारूपित और सुशोभित करें\",\n    \"sizePlaceholder\": \"आकार\",\n    \"sortAttributes\": \"विशेषताएं क्रमबद्ध करें\",\n    \"space\": \"स्पेस\",\n    \"tab\": \"टैब\",\n    \"title\": \"XML सुंदर बनाएं\",\n    \"toolInfo\": {\n      \"description\": \"यह उपकरण आपको XML डेटा को उचित इंडेंटेशन और स्पेसिंग के साथ फॉर्मेट करने की अनुमति देता है, जिससे यह अधिक पठनीय और काम करने में आसान हो जाता है।\",\n      \"title\": \"XML सुंदर बनाएं\"\n    },\n    \"useSpaces\": \"रिक्त स्थान का उपयोग करें\",\n    \"useSpacesDescription\": \"रिक्त स्थान के साथ आउटपुट इंडेंट करें\",\n    \"useTabs\": \"टैब का उपयोग करें\",\n    \"useTabsDescription\": \"टैब के साथ आउटपुट इंडेंट करें.\"\n  },\n  \"xmlValidator\": {\n    \"allowCDATA\": \"CDATA अनुमति दें\",\n    \"allowComments\": \"टिप्पणियां अनुमति दें\",\n    \"description\": \"XML स्ट्रिंग की वैधता जांचें।\",\n    \"inputTitle\": \"इनपुट XML\",\n    \"placeholder\": \"XML को यहां चिपकाएं या आयात करें...\",\n    \"resultTitle\": \"मान्यता परिणाम\",\n    \"shortDescription\": \"त्रुटियों के लिए XML कोड मान्य करें\",\n    \"showErrorDetails\": \"त्रुटि विवरण दिखाएं\",\n    \"showLineNumbers\": \"पंक्ति संख्याएं दिखाएं\",\n    \"strictMode\": \"सख्त मोड\",\n    \"title\": \"XML मान्य करें\",\n    \"toolInfo\": {\n      \"description\": \"यह टूल आपको XML सिंटैक्स और संरचना को सत्यापित करने की अनुमति देता है। यह जाँचता है कि XML सही ढंग से बना है या नहीं और किसी भी समस्या के लिए विस्तृत त्रुटि संदेश प्रदान करता है।\",\n      \"title\": \"XML मान्य करें\"\n    },\n    \"validationOptions\": \"मान्यता विकल्प\"\n  },\n  \"xmlViewer\": {\n    \"collapseAll\": \"सभी संक्षिप्त करें\",\n    \"description\": \"XML को पेड़ संरचना में देखें।\",\n    \"expandAll\": \"सभी विस्तारित करें\",\n    \"highlightSyntax\": \"सिंटैक्स हाइलाइट करें\",\n    \"inputTitle\": \"इनपुट XML\",\n    \"lineNumbers\": \"पंक्ति संख्याएं\",\n    \"resultTitle\": \"XML पेड़\",\n    \"showAttributes\": \"विशेषताएं दिखाएं\",\n    \"showTextNodes\": \"टेक्स्ट नोड दिखाएं\",\n    \"title\": \"XML व्यूअर\",\n    \"toolInfo\": {\n      \"description\": \"यह उपकरण आपको XML डेटा को पदानुक्रमित वृक्ष प्रारूप में देखने की अनुमति देता है, जिससे XML दस्तावेज़ों की संरचना का पता लगाना और समझना आसान हो जाता है।\",\n      \"title\": \"XML व्यूअर\"\n    },\n    \"viewerOptions\": \"व्यूअर विकल्प\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/audio.json",
    "content": "{\n  \"changeSpeed\": {\n    \"description\": \"オーディオファイルの再生速度を変更します。ピッチを維持しながら、オーディオの再生速度を上げたり下げたりできます。\",\n    \"inputTitle\": \"入力オーディオ\",\n    \"newAudioSpeed\": \"新しいオーディオ速度\",\n    \"outputFormat\": \"出力形式\",\n    \"resultTitle\": \"編集されたオーディオ\",\n    \"settingSpeed\": \"速度設定\",\n    \"shortDescription\": \"オーディオファイルの速度を変更する\",\n    \"speedDescription\": \"デフォルトの乗数: 2は2倍速を意味します\",\n    \"title\": \"オーディオ速度を変更する\",\n    \"toolInfo\": {\n      \"title\": \"何ですか {{title}}？\"\n    }\n  },\n  \"extractAudio\": {\n    \"description\": \"ビデオ ファイルからオーディオ トラックを抽出します。\",\n    \"extractingAudio\": \"オーディオの抽出\",\n    \"inputTitle\": \"入力ビデオ\",\n    \"outputFormat\": \"出力形式\",\n    \"outputFormatDescription\": \"抽出するオーディオの形式を選択します。\",\n    \"resultTitle\": \"抽出された音声\",\n    \"shortDescription\": \"ビデオ ファイル (MP4、MOV など) からオーディオを AAC、MP3、または WAV に抽出します。\",\n    \"title\": \"ビデオからオーディオを抽出する\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使えば、動画ファイルから音声トラックを抽出できます。AAC、MP3、WAVなど、様々な音声形式から選択できます。\",\n      \"title\": \"何ですか {{title}}？\"\n    }\n  },\n  \"mergeAudio\": {\n    \"description\": \"複数のオーディオ ファイルを順番に連結して 1 つのオーディオ ファイルに結合します。\",\n    \"inputTitle\": \"入力オーディオファイル\",\n    \"longDescription\": \"このツールを使えば、複数のオーディオファイルをアップロード順に連結し、1つのファイルにまとめることができます。ポッドキャストのセグメント、音楽トラック、その他様々なオーディオファイルを結合するのに最適です。MP3、AAC、WAVなど、様々なオーディオ形式に対応しています。\",\n    \"mergingAudio\": \"オーディオの結合\",\n    \"outputFormat\": \"出力形式\",\n    \"resultTitle\": \"統合オーディオ\",\n    \"shortDescription\": \"複数のオーディオ ファイルを 1 つに結合します (MP3、AAC、WAV)。\",\n    \"title\": \"オーディオを結合\",\n    \"toolInfo\": {\n      \"title\": \"何ですか {{title}}？\"\n    }\n  },\n  \"trim\": {\n    \"description\": \"開始時間と終了時間を指定して、オーディオ ファイルをカットおよびトリミングし、特定のセグメントを抽出します。\",\n    \"endTime\": \"終了時間\",\n    \"endTimeDescription\": \"終了時刻（HH:MM:SS形式、例：00:01:30）\",\n    \"inputTitle\": \"入力オーディオ\",\n    \"longDescription\": \"このツールを使えば、開始時間と終了時間を指定してオーディオファイルをトリミングできます。長いオーディオファイルから特定のセグメントを抽出したり、不要な部分を削除したり、短いクリップを作成したりできます。MP3、AAC、WAVなど、様々なオーディオ形式に対応しています。ポッドキャスト編集、音楽制作、その他あらゆるオーディオ編集のニーズに最適です。\",\n    \"outputFormat\": \"出力形式\",\n    \"resultTitle\": \"トリミングされたオーディオ\",\n    \"shortDescription\": \"オーディオ ファイルをトリミングして特定の時間セグメントを抽出します (MP3、AAC、WAV)。\",\n    \"startTime\": \"開始時間\",\n    \"startTimeDescription\": \"開始時刻は HH:MM:SS 形式（例: 00:00:30）\",\n    \"timeSettings\": \"時間設定\",\n    \"title\": \"オーディオをトリムする\",\n    \"toolInfo\": {\n      \"title\": \"何ですか {{title}}？\"\n    },\n    \"trimmingAudio\": \"オーディオのトリミング\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/converters.json",
    "content": "{\n  \"audioConverter\": {\n    \"title\": \"オーディオコンバーター\",\n    \"description\": \"オーディオファイルを異なるフォーマット間で変換します。\",\n    \"shortDescription\": \"オーディオファイルをさまざまなフォーマットに変換します。\",\n    \"longDescription\": \"このツールは、オーディオファイルを1つのフォーマットから別のフォーマットに変換でき、シームレスな変換のための幅広いオーディオフォーマットをサポートしています。\",\n    \"outputFormat\": \"出力フォーマット\",\n    \"outputFormatDescription\": \"目的の出力オーディオフォーマットを選択してください\",\n    \"inputTitle\": \"オーディオ入力\",\n    \"outputTitle\": \"変換済みオーディオ\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/csv.json",
    "content": "{\n  \"changeCsvSeparator\": {\n    \"description\": \"CSVファイルの区切り文字を変更します。カンマ、セミコロン、タブ、カスタム区切り文字など、さまざまなCSV形式間で変換できます。\",\n    \"shortDescription\": \"CSVファイルの区切り文字を変更する\",\n    \"title\": \"CSVセパレーターの変更\"\n  },\n  \"csvRowsToColumns\": {\n    \"description\": \"このツールは、CSV（カンマ区切り値）ファイルの行を列に変換します。入力CSVから水平方向の行を1行ずつ抽出し、90度回転させて、カンマで区切られた垂直方向の列として出力します。', longDescription: 'このツールは、CSV（カンマ区切り値）ファイルの行を列に変換します。例えば、入力CSVデータが6行の場合、出力は6列になり、行の要素は上から下へ並べられます。整形式のCSVでは、各行の値の数は同じです。ただし、行にフィールドが欠落している場合は、プログラムが修正します。欠落データを空要素で埋めるか、「missing」、「?」、「x」などのカスタム要素で置き換えるかを選択できます。変換プロセス中、ツールはCSVファイルから空行（表示情報のない行）やコメントなどの不要な情報も削除します。ツールがコメントを正しく識別できるように、オプションで行頭のコメント開始記号を指定できます。この記号は通常、ハッシュ「#」またはダブルスラッシュ「//」です。CSV-abulous!\",\n    \"longDescription\": \"このツールは、CSV（カンマ区切り値）ファイルの行を列に変換します。例えば、入力CSVデータが6行の場合、出力は6列になり、行の要素は上から下に並べられます。整形式のCSVでは、各行の値の数は同じです。ただし、行にフィールドが欠落している場合は、プログラムが修正し、利用可能なオプションから選択できます。欠落データを空の要素で埋めるか、欠落データをカスタム要素で置き換えます。\",\n    \"shortDescription\": \"CSV の行を列に変換します。\",\n    \"title\": \"CSVの行を列に変換する\"\n  },\n  \"csvToJson\": {\n    \"columnSeparator\": \"列区切り文字（例：, ; \\\\t）\",\n    \"commentSymbol\": \"コメント記号（例：#）\",\n    \"conversionOptions\": \"変換オプション\",\n    \"description\": \"区切り文字、引用符、出力フォーマットなどのカスタマイズ可能なオプションを使用して、CSVファイルをJSON形式に変換します。ヘッダー、コメント、動的な型変換をサポートします。\",\n    \"dynamicTypes\": \"動的型\",\n    \"dynamicTypesDescription\": \"数値とブール値を自動的に変換する\",\n    \"error\": \"エラー\",\n    \"errorParsing\": \"CSV 解析エラー: {{error}}\",\n    \"fieldQuote\": \"フィールド引用符（例： \\\"）\",\n    \"inputCsvFormat\": \"入力CSV形式\",\n    \"inputTitle\": \"入力CSV\",\n    \"invalidCsvFormat\": \"無効なCSV形式\",\n    \"resultTitle\": \"出力JSON\",\n    \"shortDescription\": \"CSV データを JSON 形式に変換します。\",\n    \"skipEmptyLines\": \"空行をスキップ\",\n    \"skipEmptyLinesDescription\": \"入力CSV内の空行を無視する\",\n    \"title\": \"CSVをJSONに変換する\",\n    \"toolInfo\": {\n      \"description\": \"このツールは、カンマ区切り値（CSV）ファイルをJavaScript Object Notation（JSON）データ構造に変換します。カスタマイズ可能な区切り文字、引用符、コメント記号など、様々なCSV形式に対応しています。変換機能は、最初の行をヘッダーとして扱い、空行をスキップし、数値やブール値などのデータ型を自動検出します。変換後のJSONは、データ移行、バックアップ、または他のアプリケーションへの入力として使用できます。\",\n      \"title\": \"CSV から JSON へのコンバーターとは何ですか?\"\n    },\n    \"useHeaders\": \"ヘッダーを使用する\",\n    \"useHeadersDescription\": \"最初の行を列ヘッダーとして扱う\"\n  },\n  \"csvToTsv\": {\n    \"description\": \"以下のフォームにCSVファイルをアップロードすると、自動的にTSVファイルに変換されます。ツールのオプションでは、入力CSV形式をカスタマイズできます。フィールド区切り文字、引用符、コメント記号を指定したり、空のCSV行をスキップしたり、CSVの列ヘッダーを保持するかどうかを選択したりできます。\",\n    \"longDescription\": \"このツールは、カンマ区切り値（CSV）データをタブ区切り値（TSV）データに変換します。CSVとTSVはどちらも表形式のデータを保存する一般的なファイル形式ですが、値を区切る区切り文字が異なります。CSVではカンマ（\",\n    \"shortDescription\": \"CSV データを TSV 形式に変換します。\",\n    \"title\": \"CSVをTSVに変換する\"\n  },\n  \"csvToXml\": {\n    \"description\": \"カスタマイズ可能なオプションを使用して、CSV ファイルを XML 形式に変換します。\",\n    \"shortDescription\": \"CSV データを XML 形式に変換します。\",\n    \"title\": \"CSVをXMLに変換する\"\n  },\n  \"csvToYaml\": {\n    \"description\": \"下記のフォームにCSVファイルをアップロードするだけで、自動的にYAMLファイルに変換されます。ツールオプションでは、フィールド区切り文字、フィールド引用符、コメント文字を指定して、カスタムCSV形式に対応させることができます。さらに、出力YAML形式として、CSVヘッダーを保持する形式とCSVヘッダーを除外する形式を選択できます。\",\n    \"longDescription\": \"このツールは、CSV（カンマ区切り値）データをYAML（Yet Another Markup Language）データに変換します。CSVは、行と列で構成される行列のようなデータ型を表すために使用されるシンプルな表形式です。一方、YAMLはより高度な形式（実際にはJSONのスーパーセット）であり、シリアル化のために人間が読みやすいデータを作成し、リスト、辞書、ネストされたオブジェクトをサポートしています。このプログラムは、さまざまな入力CSV形式をサポートしています。入力データは、カンマ区切り（デフォルト）、セミコロン区切り、パイプ区切り、または全く異なる区切り文字を使用できます。オプションで、データで使用する正確な区切り文字を指定できます。同様に、オプションでは、CSVフィールドを囲むために使用される引用符文字（デフォルトは二重引用符）を指定できます。また、オプションでコメント記号を指定することにより、コメントで始まる行をスキップすることもできます。これにより、不要な行をスキップしてデータを整理できます。CSVをYAMLに変換するには、2つの方法があります。最初の方法は、CSVの各行をYAMLリストに変換します。2番目の方法は、CSVの最初の行からヘッダーを抽出し、それらのヘッダーに基づいてキーを持つYAMLオブジェクトを作成します。YAML構造のインデントに使用するスペースの数を指定することで、出力YAML形式をカスタマイズすることもできます。逆変換、つまりYAMLからCSVへの変換が必要な場合は、YAMLからCSVへの変換ツールをご利用ください。CSV変換はまさに至高です！\",\n    \"shortDescription\": \"CSV ファイルを YAML ファイルにすばやく変換します。\",\n    \"title\": \"CSVをYAMLに変換する\"\n  },\n  \"findIncompleteCsvRecords\": {\n    \"checkingOptions\": \"オプションの確認\",\n    \"commentCharacterDescription\": \"コメント行の開始を示す文字を入力します。この記号で始まる行はスキップされます。\",\n    \"csvInputOptions\": \"CSV入力オプション\",\n    \"csvSeparatorDescription\": \"CSV 入力ファイル内の列を区切るために使用する文字を入力します。\",\n    \"deleteLinesWithNoData\": \"データのない行を削除する\",\n    \"deleteLinesWithNoDataDescription\": \"CSV 入力ファイルから空の行を削除します。\",\n    \"description\": \"以下のフォームにCSVファイルをアップロードするだけで、このツールは行または列に欠損値がないかどうかを自動的にチェックします。ツールのオプションでは、入力ファイルの形式（区切り文字、引用符、コメント文字の指定）を調整できます。さらに、空値のチェックを有効にしたり、空行をスキップしたり、出力時のエラーメッセージの数に制限を設定したりすることもできます。\",\n    \"findEmptyValues\": \"空の値を見つける\",\n    \"findEmptyValuesDescription\": \"空の CSV フィールドに関するメッセージを表示します (これらは欠落したフィールドではなく、何も含まれていないフィールドです)。\",\n    \"inputTitle\": \"入力CSV\",\n    \"limitNumberOfMessages\": \"メッセージ数を制限する\",\n    \"messageLimitDescription\": \"出力されるメッセージ数の制限を設定します。\",\n    \"quoteCharacterDescription\": \"CSV 入力フィールドを引用するために使用する引用符文字を入力します。\",\n    \"resultTitle\": \"CSVステータス\",\n    \"shortDescription\": \"CSV 内で値が欠落している行と列をすばやく見つけます。\",\n    \"title\": \"不完全なCSVレコードを見つける\",\n    \"toolInfo\": {\n      \"title\": \"何ですか {{title}}？\"\n    }\n  },\n  \"insertCsvColumns\": {\n    \"appendColumns\": \"列を追加する\",\n    \"commentCharacterDescription\": \"コメント行の開始を示す文字を入力します。この記号で始まる行はスキップされます。\",\n    \"csvOptions\": \"CSVオプション\",\n    \"csvSeparator\": \"CSVセパレーター\",\n    \"csvToInsert\": \"挿入するCSV\",\n    \"csvToInsertDescription\": \"CSVに挿入したい列を1つ以上入力してください。列区切りに使用する文字は、CSV入力ファイル内の文字と同じである必要があります。注：空白行は無視されます。\",\n    \"customFillDescription\": \"入力 CSV ファイルが不完全 (値が欠落している) な場合は、レコードに空のフィールドまたはカスタム シンボルを追加して、整形式の CSV を作成しますか?\",\n    \"customFillValueDescription\": \"このカスタム値を使用して、不足しているフィールドを入力します。(上記の「カスタム値」モードでのみ機能します。)\",\n    \"customPosition\": \"カスタム位置\",\n    \"customPositionOptionsDescription\": \"CSV ファイルに列を挿入する方法を選択します。\",\n    \"description\": \"指定された位置に CSV データに新しい列を追加します。\",\n    \"fillWithCustomValues\": \"関税額を入力する\",\n    \"fillWithEmptyValues\": \"空の値を入力する\",\n    \"headerName\": \"ヘッダー名\",\n    \"headerNameDescription\": \"後ろに列を挿入する列のヘッダー。\",\n    \"inputTitle\": \"入力CSV\",\n    \"insertingPositionDescription\": \"CSV ファイル内の列を挿入する場所を指定します。\",\n    \"position\": \"位置\",\n    \"positionOptions\": \"ポジションオプション\",\n    \"prependColumns\": \"列を先頭に追加する\",\n    \"quoteCharDescription\": \"CSV 入力フィールドを引用するために使用する引用符文字を入力します。\",\n    \"resultTitle\": \"CSV出力\",\n    \"rowNumberDescription\": \"後ろに列を挿入する列の番号。\",\n    \"separatorDescription\": \"CSV 入力ファイル内の列を区切るために使用する文字を入力します。\",\n    \"shortDescription\": \"CSV ファイル内の任意の場所に 1 つ以上の新しい列をすばやく挿入します。\",\n    \"title\": \"CSV列を挿入する\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使用すると、CSVデータの指定した位置に新しい列を挿入できます。ヘッダー名または列番号に基づいて、任意の位置に列を追加、追加、または挿入できます。\",\n      \"title\": \"CSV列の挿入\"\n    }\n  },\n  \"swapCsvColumns\": {\n    \"description\": \"以下のフォームにCSVファイルをアップロードし、入れ替えたい列を指定するだけで、ツールは出力ファイル内で指定された列の位置を自動的に変更します。ツールのオプションでは、入れ替えたい列の位置や列名を指定できるほか、不完全なデータを修正したり、空のレコードやコメントアウトされたレコードを削除したりすることもできます。\",\n    \"longDescription\": \"このツールは、CSVデータの列の位置を入れ替えることでデータを再構成します。列を入れ替えることで、頻繁に使用するデータをまとめて配置したり、先頭に配置したりすることで、データの比較や編集が容易になり、CSVファイルの読みやすさが向上します。例えば、最初の列を最後の列と入れ替えたり、2番目の列を3番目の列と入れ替えたりすることができます。列の位置に基づいて列を入れ替えるには、\",\n    \"shortDescription\": \"CSV 列を並べ替えます。\",\n    \"title\": \"CSV列の入れ替え\"\n  },\n  \"transposeCsv\": {\n    \"description\": \"以下のフォームにCSVファイルをアップロードするだけで、このツールが自動的にCSVを転置します。ツールのオプションでは、CSV内のコメント行の先頭文字を指定して削除できます。また、CSVが不完全（欠損値）な場合は、欠損値を空文字または任意の文字に置き換えることができます。\",\n    \"longDescription\": \"このツールは、カンマ区切り値（CSV）を転置します。CSVをデータの行列として扱い、すべての要素を主対角線を挟んで反転します。出力には入力と同じCSVデータが含まれますが、すべての行が列になり、すべての列が行になります。転置後、CSVファイルの次元は逆になります。たとえば、入力ファイルに4列3行がある場合、出力ファイルは3列4行になります。変換中に、プログラムは不要な行のデータを削除し、不完全なデータを修正します。具体的には、ツールはオプションで設定できる特定の文字で始まるすべての空のレコードとコメントを自動的に削除します。さらに、CSVデータが破損または失われた場合、ユーティリティはオプションで指定できる空のフィールドまたはカスタムフィールドでファイルを補完します。CSV-abulous!\",\n    \"shortDescription\": \"CSV ファイルをすばやく転置します。\",\n    \"title\": \"CSV転置\"\n  },\n  \"tsvToJson\": {\n    \"description\": \"TSV（タブ区切り値）データをJSON形式に変換します。表形式のデータを構造化されたJSONオブジェクトに変換します。\",\n    \"shortDescription\": \"TSVをJSON形式に変換する\",\n    \"title\": \"TSVからJSONへ\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/image.json",
    "content": "{\n  \"changeColors\": {\n    \"description\": \"世界\",\n    \"shortDescription\": \"画像内の色を素早く入れ替える\",\n    \"title\": \"画像の色を変更する\"\n  },\n  \"changeOpacity\": {\n    \"description\": \"画像の透明度を簡単に調整できます。画像をアップロードし、スライダーを使って0（完全に透明）から1（完全に不透明）の間で希望の不透明度を設定し、変更した画像をダウンロードするだけです。\",\n    \"shortDescription\": \"画像の透明度を調整する\",\n    \"title\": \"画像の不透明度を変更する\"\n  },\n  \"compress\": {\n    \"compressedSize\": \"圧縮サイズ\",\n    \"compressionOptions\": \"圧縮オプション\",\n    \"description\": \"品質を維持しながら画像ファイルのサイズを縮小します。\",\n    \"failedToCompress\": \"画像の圧縮に失敗しました。もう一度お試しください。\",\n    \"fileSizes\": \"ファイルサイズ\",\n    \"inputTitle\": \"入力画像\",\n    \"maxFileSizeDescription\": \"最大ファイルサイズ（メガバイト）\",\n    \"originalSize\": \"オリジナルサイズ\",\n    \"qualityDescription\": \"画像品質のパーセンテージ（低いほどファイルサイズが小さくなります）\",\n    \"resultTitle\": \"圧縮画像\",\n    \"shortDescription\": \"適切な品質を維持しながら画像を圧縮してファイル サイズを縮小します。\",\n    \"title\": \"画像を圧縮\"\n  },\n  \"compressPng\": {\n    \"description\": \"これはPNG画像を圧縮するプログラムです。PNG画像を入力エリアに貼り付けると、プログラムがすぐに圧縮し、出力エリアに結果を表示します。オプションでは、圧縮レベルを調整したり、圧縮前の画像ファイルサイズと新しい画像ファイルサイズを確認したりできます。\",\n    \"shortDescription\": \"PNGを素早く圧縮する\",\n    \"title\": \"PNGを圧縮する\"\n  },\n  \"convertJgpToPng\": {\n    \"description\": \"JPG画像をPNGに素早く変換できます。左側のエディタにPNG画像をインポートするだけです。\",\n    \"shortDescription\": \"JPG画像をPNGに素早く変換\",\n    \"title\": \"JPGをPNGに変換する\"\n  },\n  \"convertToJpg\": {\n    \"description\": \"さまざまな画像形式 (PNG、GIF、TIF、PSD、SVG、WEBP、HEIC、RAW) を、カスタマイズ可能な品質と背景色の設定で JPG に変換します。\",\n    \"shortDescription\": \"品質管理しながら画像をJPGに変換する\",\n    \"title\": \"画像をJPGに変換する\"\n  },\n  \"createTransparent\": {\n    \"description\": \"世界\",\n    \"shortDescription\": \"画像を素早く透明にする\",\n    \"title\": \"透明なPNGを作成する\"\n  },\n  \"crop\": {\n    \"description\": \"画像をトリミングして不要な部分を削除します。\",\n    \"inputTitle\": \"入力画像\",\n    \"resultTitle\": \"切り抜かれた画像\",\n    \"shortDescription\": \"画像を素早く切り取ります。\",\n    \"title\": \"画像をトリミング\"\n  },\n  \"editor\": {\n    \"description\": \"切り抜き、回転、注釈、色調整、透かしの追加など、高度な画像編集ツールを備えた高度な画像エディター。プロ仕様のツールを使って、ブラウザ上で直接画像を編集できます。\",\n    \"shortDescription\": \"高度なツールと機能で画像を編集する\",\n    \"title\": \"画像エディタ\"\n  },\n  \"imageToText\": {\n    \"description\": \"光学文字認識 (OCR) を使用して画像 (JPG、PNG) からテキストを抽出します。\",\n    \"shortDescription\": \"OCR を使用して画像からテキストを抽出します。\",\n    \"title\": \"画像からテキストへ（OCR）\"\n  },\n  \"qrCode\": {\n    \"description\": \"URL、テキスト、電子メール、電話、SMS、WiFi、vCard など、さまざまなデータ タイプの QR コードを生成します。\",\n    \"shortDescription\": \"さまざまなデータ形式に合わせてカスタマイズされた QR コードを作成します。\",\n    \"title\": \"QRコードジェネレーター\"\n  },\n  \"removeBackground\": {\n    \"description\": \"世界\",\n    \"shortDescription\": \"画像から背景を自動的に削除する\",\n    \"title\": \"画像から背景を削除する\"\n  },\n  \"resize\": {\n    \"description\": \"画像をさまざまな寸法に合わせてサイズ変更します。\",\n    \"dimensionType\": \"ディメンションタイプ\",\n    \"heightDescription\": \"高さ（ピクセル単位）\",\n    \"inputTitle\": \"入力画像\",\n    \"maintainAspectRatio\": \"アスペクト比を維持\",\n    \"maintainAspectRatioDescription\": \"画像の元のアスペクト比を維持します。\",\n    \"percentage\": \"パーセンテージ\",\n    \"percentageDescription\": \"元のサイズの割合（例：半分のサイズの場合は 50、2 倍のサイズの場合は 200）\",\n    \"resizeByPercentage\": \"パーセンテージでサイズを変更する\",\n    \"resizeByPercentageDescription\": \"元のサイズのパーセンテージを指定してサイズを変更します。\",\n    \"resizeByPixels\": \"ピクセル単位でサイズ変更\",\n    \"resizeByPixelsDescription\": \"ピクセル単位で寸法を指定してサイズを変更します。\",\n    \"resizeMethod\": \"サイズ変更方法\",\n    \"resultTitle\": \"サイズ変更された画像\",\n    \"setHeight\": \"高さの設定\",\n    \"setHeightDescription\": \"高さをピクセル単位で指定し、アスペクト比に基づいて幅を計算します。\",\n    \"setWidth\": \"幅を設定する\",\n    \"setWidthDescription\": \"幅をピクセル単位で指定し、アスペクト比に基づいて高さを計算します。\",\n    \"shortDescription\": \"画像のサイズを簡単に変更できます。\",\n    \"title\": \"画像のサイズ変更\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使えば、JPG、PNG、SVG、GIF画像のサイズを変更できます。ピクセル単位またはパーセンテージでサイズを指定し、元のアスペクト比を維持するオプションも用意されています。\",\n      \"title\": \"画像のサイズ変更\"\n    },\n    \"widthDescription\": \"幅（ピクセル単位）\"\n  },\n  \"rotate\": {\n    \"description\": \"指定された角度で画像を回転します。\",\n    \"shortDescription\": \"画像を簡単に回転します。\",\n    \"title\": \"画像を回転する\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/json.json",
    "content": "{\n  \"comparison\": {\n    \"description\": \"2 つの JSON オブジェクトを比較して、構造と値の違いを識別します。\",\n    \"shortDescription\": \"2つのJSONオブジェクトの違いを見つける\",\n    \"title\": \"JSONを比較する\"\n  },\n  \"escapeJson\": {\n    \"description\": \"JSON文字列内の特殊文字をエスケープします。JSONデータを適切にエスケープされた形式に変換して、安全な転送や保存を実現します。\",\n    \"shortDescription\": \"JSON内の特殊文字をエスケープする\",\n    \"title\": \"JSON のエスケープ\"\n  },\n  \"jsonToXml\": {\n    \"description\": \"JSONデータをXML形式に変換します。構造化されたJSONオブジェクトを整形式のXMLドキュメントに変換します。\",\n    \"shortDescription\": \"JSONをXML形式に変換する\",\n    \"title\": \"JSONからXMLへ\"\n  },\n  \"minify\": {\n    \"description\": \"JSON から不要な空白をすべて削除します。\",\n    \"inputTitle\": \"入力JSON\",\n    \"resultTitle\": \"縮小されたJSON\",\n    \"shortDescription\": \"空白を削除してJSONを縮小する\",\n    \"title\": \"JSONを縮小する\",\n    \"toolInfo\": {\n      \"description\": \"JSONの縮小とは、JSONデータの妥当性を維持しながら、不要な空白文字をすべて削除するプロセスです。これには、JSONを正しく解析するために不要なスペース、改行、インデントなどが含まれます。縮小によりJSONデータのサイズが縮小され、データ構造と値は全く同じまま、保存と転送の効率が向上します。\",\n      \"title\": \"JSON の縮小とは何ですか?\"\n    }\n  },\n  \"prettify\": {\n    \"description\": \"適切なインデントとスペースを使用して JSON をフォーマットします。\",\n    \"indentation\": \"インデント\",\n    \"inputTitle\": \"入力JSON\",\n    \"resultTitle\": \"整形されたJSON\",\n    \"shortDescription\": \"JSON コードをフォーマットして美しくする\",\n    \"title\": \"JSONを美しくする\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使用すると、JSON データを適切なインデントとスペースでフォーマットできるため、読みやすく、操作しやすくなります。\",\n      \"title\": \"JSONを美しくする\"\n    },\n    \"useSpaces\": \"スペースを使用する\",\n    \"useSpacesDescription\": \"スペースで出力をインデントする\",\n    \"useTabs\": \"タブを使用する\",\n    \"useTabsDescription\": \"出力をタブでインデントします。\"\n  },\n  \"stringify\": {\n    \"description\": \"JavaScriptオブジェクトをJSON文字列形式に変換します。データ構造をJSON文字列にシリアル化して保存または転送します。\",\n    \"shortDescription\": \"オブジェクトをJSON文字列に変換する\",\n    \"title\": \"JSONを文字列化する\"\n  },\n  \"validateJson\": {\n    \"description\": \"JSON が有効かつ整形式であるかどうかを確認します。\",\n    \"inputTitle\": \"入力JSON\",\n    \"invalidJson\": \"❌ {{error}}\",\n    \"resultTitle\": \"検証結果\",\n    \"shortDescription\": \"JSONコードのエラーを検証する\",\n    \"title\": \"JSONを検証する\",\n    \"toolInfo\": {\n      \"description\": \"JSON（JavaScript Object Notation）は軽量なデータ交換形式です。JSON検証は、データの構造がJSON標準に準拠していることを確認します。有効なJSONオブジェクトには、以下の要素が必要です。- プロパティ名が二重引用符で囲まれている。- 中括弧{}が適切にバランスされている。- 最後のキーと値のペアの後にカンマが付いていない。- オブジェクトと配列が適切にネストされている。このツールは入力されたJSONを検証し、一般的なエラーを特定して修正するためのフィードバックを提供します。\",\n      \"title\": \"JSON 検証とは何ですか?\"\n    },\n    \"validJson\": \"✅ 有効なJSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/list.json",
    "content": "{\n  \"duplicate\": {\n    \"concatenate\": \"連結\",\n    \"concatenateDescription\": \"コピーを連結する（チェックを外すと、アイテムが織り交ぜられます）\",\n    \"copyDescription\": \"コピー数（端数も可）\",\n    \"description\": \"リストアイテムを複製するための、世界で最もシンプルなブラウザベースのユーティリティです。リストを入力し、複製条件を指定するだけで、アイテムのコピーを作成できます。データの拡張、テスト、繰り返しパターンの作成に最適です。\",\n    \"duplicationOptions\": \"複製オプション\",\n    \"error\": \"エラー\",\n    \"example1Description\": \"この例では、単語のリストを複製する方法を示します。\",\n    \"example1Title\": \"単純な複製\",\n    \"example2Description\": \"この例では、リストを逆の順序で複製する方法を示します。\",\n    \"example2Title\": \"逆複製\",\n    \"example3Description\": \"この例では、項目を連結するのではなく織り交ぜる方法を示します。\",\n    \"example3Title\": \"織り交ぜたアイテム\",\n    \"example4Description\": \"この例では、リストを小数部数で複製する方法を示します。\",\n    \"example4Title\": \"部分的な複製\",\n    \"examples\": {\n      \"fractional\": {\n        \"description\": \"この例では、リストを小数部数で複製する方法を示します。\",\n        \"title\": \"部分的な複製\"\n      },\n      \"interweave\": {\n        \"description\": \"この例では、項目を連結するのではなく織り交ぜる方法を示します。\",\n        \"title\": \"織り交ぜたアイテム\"\n      },\n      \"reverse\": {\n        \"description\": \"この例では、リストを逆の順序で複製する方法を示します。\",\n        \"title\": \"逆複製\"\n      },\n      \"simple\": {\n        \"description\": \"この例では、単語のリストを複製する方法を示します。\",\n        \"title\": \"単純な複製\"\n      }\n    },\n    \"inputTitle\": \"入力リスト\",\n    \"joinSeparatorDescription\": \"重複したリストを結合するための区切り文字\",\n    \"resultTitle\": \"重複リスト\",\n    \"reverse\": \"逆行する\",\n    \"reverseDescription\": \"重複したアイテムを元に戻す\",\n    \"shortDescription\": \"指定した条件でリスト項目を複製する\",\n    \"splitByRegex\": \"正規表現で分割\",\n    \"splitBySymbol\": \"シンボルで分割\",\n    \"splitOptions\": \"分割オプション\",\n    \"splitSeparatorDescription\": \"リストを分割するための区切り文字\",\n    \"title\": \"重複\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使うと、リスト内の項目を複製できます。複製する数（小数点以下を含む）を指定したり、項目を連結するか織り交ぜるかを制御したり、複製した項目を反転させたりすることも可能です。繰り返しパターンの作成、テストデータの生成、あるいは予測可能な内容のリスト拡張などに便利です。\",\n      \"title\": \"リストの重複\"\n    },\n    \"unknownError\": \"不明なエラーが発生しました\",\n    \"validation\": {\n      \"copyMustBeNumber\": \"コピー数は数字でなければなりません\",\n      \"copyMustBePositive\": \"コピー数は正の数でなければなりません\",\n      \"copyRequired\": \"コピー数が必要です\",\n      \"joinSeparatorRequired\": \"結合セパレーターは必須です\",\n      \"separatorRequired\": \"セパレータは必須です\"\n    }\n  },\n  \"findMostPopular\": {\n    \"description\": \"リスト内で最も人気のあるアイテムを見つけるための、世界で最もシンプルなブラウザベースのユーティリティです。リストを入力するだけで、最も頻繁に表示されるアイテムを瞬時に表示します。データ分析、トレンドの特定、共通要素の発見に最適です。\",\n    \"displayFormatDescription\": \"最も人気のあるリスト項目を表示するにはどうすればいいですか?\",\n    \"displayOptions\": {\n      \"count\": \"アイテム数を表示\",\n      \"percentage\": \"アイテムの割合を表示\",\n      \"total\": \"アイテムの合計を表示\"\n    },\n    \"extractListItems\": \"リスト項目を抽出するにはどうすればいいですか?\",\n    \"ignoreItemCase\": \"項目の大文字と小文字を区別しない\",\n    \"ignoreItemCaseDescription\": \"リスト項目をすべて小文字で比較します。\",\n    \"inputTitle\": \"入力リスト\",\n    \"itemComparison\": \"アイテム比較\",\n    \"outputFormat\": \"トップ項目の出力形式\",\n    \"removeEmptyItems\": \"空のアイテムを削除する\",\n    \"removeEmptyItemsDescription\": \"比較から空の項目を無視します。\",\n    \"resultTitle\": \"最も人気のある商品\",\n    \"shortDescription\": \"最も頻繁に発生するアイテムを見つける\",\n    \"sortOptions\": {\n      \"alphabetic\": \"アルファベット順に並べ替え\",\n      \"count\": \"カウントで並べ替え\"\n    },\n    \"sortingMethodDescription\": \"並べ替え方法を選択します。\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"入力リスト項目を正規表現で区切ります。\",\n        \"title\": \"正規表現を使用して分割する\"\n      },\n      \"symbol\": {\n        \"description\": \"入力リスト項目を文字で区切ります。\",\n        \"title\": \"分割にシンボルを使用する\"\n      }\n    },\n    \"splitSeparatorDescription\": \"区切り記号または正規表現を設定します。\",\n    \"title\": \"最も人気のあるものを探す\",\n    \"trimItems\": \"上位リスト項目をトリミングする\",\n    \"trimItemsDescription\": \"項目を比較する前に先頭と末尾のスペースを削除します\"\n  },\n  \"findUnique\": {\n    \"caseSensitiveItems\": \"大文字と小文字を区別する項目\",\n    \"caseSensitiveItemsDescription\": \"大文字と小文字が異なる項目をリスト内の一意の要素として出力します。\",\n    \"delimiterDescription\": \"区切り記号または正規表現を設定します。\",\n    \"description\": \"リスト内の一意の項目を見つけるための、世界で最もシンプルなブラウザベースのユーティリティです。リストを入力すると、重複を除いたすべての一意の値が瞬時に取得されます。データのクリーニング、重複除去、または異なる要素の検索に最適です。\",\n    \"findAbsolutelyUniqueItems\": \"絶対にユニークなアイテムを見つける\",\n    \"findAbsolutelyUniqueItemsDescription\": \"リストの項目のうち、単一のコピー内に存在するものだけを表示します。\",\n    \"inputListDelimiter\": \"入力リスト区切り文字\",\n    \"inputTitle\": \"入力リスト\",\n    \"outputListDelimiter\": \"出力リスト区切り文字\",\n    \"resultTitle\": \"ユニークなアイテム\",\n    \"shortDescription\": \"リスト内のユニークなアイテムを見つける\",\n    \"skipEmptyItems\": \"空の項目をスキップ\",\n    \"skipEmptyItemsDescription\": \"出力に空のリスト項目を含めないでください。\",\n    \"title\": \"ユニークなものを見つける\",\n    \"trimItems\": \"リスト項目をトリムする\",\n    \"trimItemsDescription\": \"項目を比較する前に先頭と末尾のスペースを削除します。\",\n    \"uniqueItemOptions\": \"ユニークなアイテムオプション\"\n  },\n  \"group\": {\n    \"deleteEmptyItems\": \"空のアイテムを削除する\",\n    \"deleteEmptyItemsDescription\": \"空の項目は無視し、グループに含めません。\",\n    \"description\": \"リスト項目をグループ化するための、世界で最もシンプルなブラウザベースのユーティリティです。リストを入力し、グループ化の条件を指定するだけで、項目を論理的なグループに整理できます。データの分類、情報の整理、構造化されたリストの作成に最適です。カスタムセパレーターと様々なグループ化オプションをサポートしています。\",\n    \"emptyItemsAndPadding\": \"空の項目とパディング\",\n    \"groupNumberDescription\": \"グループ内のアイテム数\",\n    \"groupSeparatorDescription\": \"グループ区切り文字\",\n    \"groupSizeAndSeparators\": \"グループサイズと区切り\",\n    \"inputItemSeparator\": \"入力項目セパレーター\",\n    \"inputTitle\": \"入力リスト\",\n    \"itemSeparatorDescription\": \"項目区切り文字\",\n    \"leftWrapDescription\": \"グループの左折り返し記号。\",\n    \"padNonFullGroups\": \"非フルグループのパディング\",\n    \"padNonFullGroupsDescription\": \"いっぱいでないグループをカスタム項目で埋めます (以下に入力)。\",\n    \"paddingCharDescription\": \"この文字または項目を使用して、完全でないグループを埋め込みます。\",\n    \"resultTitle\": \"グループ化されたアイテム\",\n    \"rightWrapDescription\": \"グループの右折り返し記号。\",\n    \"shortDescription\": \"共通のプロパティでリスト項目をグループ化する\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"入力リスト項目を正規表現で区切ります。\",\n        \"title\": \"正規表現を使用して分割する\"\n      },\n      \"symbol\": {\n        \"description\": \"入力リスト項目を文字で区切ります。\",\n        \"title\": \"分割にシンボルを使用する\"\n      }\n    },\n    \"splitSeparatorDescription\": \"区切り記号または正規表現を設定します。\",\n    \"title\": \"グループ\"\n  },\n  \"reverse\": {\n    \"description\": \"これは、すべてのリスト項目を逆順に表示する、非常にシンプルなブラウザベースのアプリケーションです。入力項目は任意の記号で区切ることができ、逆順に表示するリスト項目の区切り文字を変更することもできます。\",\n    \"inputTitle\": \"入力リスト\",\n    \"itemSeparator\": \"アイテムセパレーター\",\n    \"itemSeparatorDescription\": \"区切り記号または正規表現を設定します。\",\n    \"outputListOptions\": \"出力リストオプション\",\n    \"outputSeparatorDescription\": \"出力リスト項目のセパレーター。\",\n    \"resultTitle\": \"逆リスト\",\n    \"shortDescription\": \"リストを素早く逆順にする\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"入力リスト項目を正規表現で区切ります。\",\n        \"title\": \"正規表現を使用して分割する\"\n      },\n      \"symbol\": {\n        \"description\": \"入力リスト項目を文字で区切ります。\",\n        \"title\": \"分割にシンボルを使用する\"\n      }\n    },\n    \"splitterMode\": \"スプリッターモード\",\n    \"title\": \"逆行する\",\n    \"toolInfo\": {\n      \"description\": \"このユーティリティを使えば、リスト内の項目の順序を逆にすることができます。このユーティリティはまず入力リストを個々の項目に分割し、最後の項目から最初の項目まで反復処理を行い、反復処理中に各項目を出力に出力します。入力リストには、数字、文字列、単語、文章など、テキストデータとして表現できるあらゆるものを含めることができます。入力項目の区切り文字には正規表現も使用できます。例えば、正規表現 /[;,]/ を使用すると、カンマ区切りまたはセミコロン区切りの項目を使用できます。入力リストと出力リストの項目の区切り文字は、オプションでカスタマイズできます。デフォルトでは、入力リストと出力リストの両方がカンマ区切りです。まさにリストの素晴らしさ！\",\n      \"title\": \"リストリバーサーとは何ですか?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"リスト項目を回転させる、世界で最もシンプルなブラウザベースのユーティリティです。リストを入力し、回転量を指定すると、指定した数だけ項目を移動できます。データ操作、循環シフト、リストの並べ替えに最適です。\",\n    \"shortDescription\": \"リスト項目を指定された位置で回転する\",\n    \"title\": \"回転\"\n  },\n  \"shuffle\": {\n    \"delimiterDescription\": \"区切り記号または正規表現を設定します。\",\n    \"description\": \"リスト項目をシャッフルする、世界で最もシンプルなブラウザベースのユーティリティです。リストを入力すると、項目がランダムな順序で並んだランダムバージョンが瞬時に作成されます。バラエティ豊かなリストの作成、ランダム性のテスト、順序付けられたデータの混合などに最適です。\",\n    \"inputListSeparator\": \"入力リストセパレーター\",\n    \"inputTitle\": \"入力リスト\",\n    \"joinSeparatorDescription\": \"ランダム化されたリストではこの区切り文字を使用します。\",\n    \"outputLengthDescription\": \"これだけの数のランダムアイテムを出力する\",\n    \"resultTitle\": \"シャッフルされたリスト\",\n    \"shortDescription\": \"リスト項目の順序をランダムにする\",\n    \"shuffledListLength\": \"シャッフルリストの長さ\",\n    \"shuffledListSeparator\": \"シャッフルリストセパレーター\",\n    \"title\": \"シャッフル\"\n  },\n  \"sort\": {\n    \"caseSensitive\": \"大文字と小文字を区別する並べ替え\",\n    \"caseSensitiveDescription\": \"大文字と小文字を別々に並べ替えます。昇順リストでは、大文字が小文字よりも前に表示されます。（アルファベット順の並べ替えモードでのみ機能します。）\",\n    \"description\": \"リスト項目を並べ替えるための、世界で最もシンプルなブラウザベースのユーティリティです。リストを入力し、並べ替え条件を指定することで、項目を昇順または降順に並べ替えることができます。データの整理、テキスト処理、順序付きリストの作成に最適です。\",\n    \"inputItemSeparator\": \"入力項目セパレーター\",\n    \"inputTitle\": \"入力リスト\",\n    \"joinSeparatorDescription\": \"この記号は、ソートされたリスト内の項目間の結合子として使用します。\",\n    \"orderDescription\": \"並べ替え順序を選択します。\",\n    \"orderOptions\": {\n      \"decreasing\": \"降順\",\n      \"increasing\": \"昇順\"\n    },\n    \"removeDuplicates\": \"重複を削除する\",\n    \"removeDuplicatesDescription\": \"重複したリスト項目を削除します。\",\n    \"resultTitle\": \"ソートされたリスト\",\n    \"shortDescription\": \"リスト項目を指定された順序で並べ替える\",\n    \"sortMethod\": \"ソート方法\",\n    \"sortMethodDescription\": \"並べ替え方法を選択します。\",\n    \"sortOptions\": {\n      \"alphabetic\": \"アルファベット順に並べ替え\",\n      \"length\": \"長さで並べ替え\",\n      \"numeric\": \"数値順に並べ替える\"\n    },\n    \"sortedItemProperties\": \"並べ替えられたアイテムのプロパティ\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"入力リスト項目を正規表現で区切ります。\",\n        \"title\": \"正規表現を使用して分割する\"\n      },\n      \"symbol\": {\n        \"description\": \"入力リスト項目を文字で区切ります。\",\n        \"title\": \"分割にシンボルを使用する\"\n      }\n    },\n    \"splitSeparatorDescription\": \"区切り記号または正規表現を設定します。\",\n    \"title\": \"選別\"\n  },\n  \"truncate\": {\n    \"description\": \"リストを切り捨てるための、世界で最もシンプルなブラウザベースのユーティリティです。リストを入力し、保持するアイテムの最大数を指定します。データ処理、リスト管理、コンテンツの長さ制限などに最適です。\",\n    \"shortDescription\": \"リストを指定された項目数に切り捨てる\",\n    \"title\": \"切り捨て\"\n  },\n  \"unwrap\": {\n    \"description\": \"リスト項目を展開するための、世界で最もシンプルなブラウザベースのユーティリティです。展開されたリストを入力し、展開条件を指定することで、整理された項目をフラット化できます。データ処理、テキスト操作、構造化リストからのコンテンツ抽出に最適です。\",\n    \"shortDescription\": \"構造化された形式からリスト項目をアンラップする\",\n    \"title\": \"ラップを解除\"\n  },\n  \"wrap\": {\n    \"description\": \"各リスト項目の前後にテキストを追加します。\",\n    \"inputTitle\": \"入力リスト\",\n    \"joinSeparatorDescription\": \"ラップされたリストを結合するためのセパレーター\",\n    \"leftTextDescription\": \"各項目の前に追加するテキスト\",\n    \"removeEmptyItems\": \"空のアイテムを削除する\",\n    \"resultTitle\": \"ラップされたリスト\",\n    \"rightTextDescription\": \"各項目の後に追加するテキスト\",\n    \"shortDescription\": \"指定した条件でリスト項目を囲む\",\n    \"splitByRegex\": \"正規表現で分割\",\n    \"splitBySymbol\": \"シンボルで分割\",\n    \"splitOptions\": \"分割オプション\",\n    \"splitSeparatorDescription\": \"リストを分割するための区切り文字\",\n    \"title\": \"包む\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使うと、リスト内の各項目の前後にテキストを追加できます。左右に異なるテキストを指定したり、リストの処理方法を制御したりできます。リスト項目に引用符、括弧、その他の書式を追加したり、さまざまな形式に合わせてデータを準備したり、構造化テキストを作成したりするのに役立ちます。\",\n      \"title\": \"リストの折り返し\"\n    },\n    \"wrapOptions\": \"ラップオプション\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/number.json",
    "content": "{\n  \"arithmeticSequence\": {\n    \"commonDifferenceDescription\": \"用語間の共通差異（d）\",\n    \"description\": \"カスタマイズ可能なパラメータを使用して算術シーケンスを生成します。\",\n    \"firstTermDescription\": \"数列の最初の項 (a₁)\",\n    \"numberOfTermsDescription\": \"生成する項の数 (n)\",\n    \"outputFormat\": \"出力形式\",\n    \"resultTitle\": \"生成されたシーケンス\",\n    \"separatorDescription\": \"用語間の区切り文字\",\n    \"sequenceParameters\": \"シーケンスパラメータ\",\n    \"shortDescription\": \"等差数列を生成する\",\n    \"title\": \"等差数列\",\n    \"toolInfo\": {\n      \"description\": \"等差数列とは、連続する各項の差が一定である数列です。この一定差は公差と呼ばれます。最初の項 (a₁) と公差 (d) が与えられれば、各項は前の項に公差を加えることで求められます。\",\n      \"title\": \"等差数列とは何ですか?\"\n    }\n  },\n  \"generate\": {\n    \"arithmeticSequenceOption\": \"等差数列オプション\",\n    \"description\": \"カスタマイズ可能なパラメータを使用して数値のシーケンスを生成します。\",\n    \"numberOfElementsDescription\": \"シーケンス内の要素の数。\",\n    \"resultTitle\": \"生成された番号\",\n    \"separator\": \"セパレーター\",\n    \"separatorDescription\": \"等差数列内の要素をこの文字で区切ります。\",\n    \"shortDescription\": \"指定された範囲内で乱数を生成する\",\n    \"startSequenceDescription\": \"この番号からシーケンスを開始します。\",\n    \"stepDescription\": \"各要素をこの量だけ増加します\",\n    \"title\": \"生成する\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使用すると、カスタマイズ可能なパラメータを使用して数値列を生成できます。開始値、ステップサイズ、要素数を指定できます。\",\n      \"title\": \"数字を生成する\"\n    }\n  },\n  \"ohmsLaw\": {\n    \"description\": \"電圧、電流、抵抗を計算します\",\n    \"longDescription\": \"この計算機は、オームの法則（V = I × R）を用いて、他の2つの電気パラメータが既知の場合、3つの電気パラメータのいずれかを決定します。オームの法則は、電圧（V）、電流（I）、抵抗（R）の関係を記述する電気工学の基本原理です。このツールは、電子工学愛好家、電気技術者、そして回路設計に取り組む学生にとって、電気設計における未知の値を素早く解くために不可欠です。\",\n    \"shortDescription\": \"オームの法則を使って電気回路の電圧、電流、抵抗を計算する\",\n    \"title\": \"オームの法則\"\n  },\n  \"randomNumberGenerator\": {\n    \"description\": \"カスタマイズ可能なオプションを使用して、指定された範囲内で乱数を生成します。\",\n    \"error\": {\n      \"generationFailed\": \"乱数を生成できませんでした。入力パラメータを確認してください。\"\n    },\n    \"info\": {\n      \"description\": \"乱数ジェネレータは、指定された範囲内で予測不可能な数値を生成します。このツールは、暗号的に安全な乱数生成技術を用いて、真にランダムな結果を保証します。シミュレーション、ゲーム、統計的サンプリング、テストなどのシナリオに役立ちます。\",\n      \"title\": \"乱数ジェネレータとは何ですか?\"\n    },\n    \"longDescription\": \"整数または小数、重複の許可/禁止、結果の並べ替えなどのオプションを使用して、指定範囲内で乱数を生成します。シミュレーション、テスト、ゲーム、統計分析に最適です。\",\n    \"options\": {\n      \"generation\": {\n        \"allowDecimals\": {\n          \"description\": \"整数の代わりに小数を生成する\",\n          \"title\": \"小数点を許可する\"\n        },\n        \"allowDuplicates\": {\n          \"description\": \"同じ数字を複数回表示できるようにする\",\n          \"title\": \"重複を許可する\"\n        },\n        \"countDescription\": \"生成する乱数の数（1～10,000）\",\n        \"sortResults\": {\n          \"description\": \"生成された数字を昇順に並べ替える\",\n          \"title\": \"結果を並べ替える\"\n        },\n        \"title\": \"生成オプション\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"生成された数字を区切る文字\",\n        \"title\": \"出力設定\"\n      },\n      \"range\": {\n        \"maxDescription\": \"最大値（含む）\",\n        \"minDescription\": \"最小値（含む）\",\n        \"title\": \"範囲設定\"\n      }\n    },\n    \"result\": {\n      \"count\": \"カウント\",\n      \"hasDuplicates\": \"重複が含まれています\",\n      \"isSorted\": \"ソート済み\",\n      \"range\": \"範囲\",\n      \"title\": \"生成された乱数\"\n    },\n    \"shortDescription\": \"カスタム範囲内で乱数を生成する\",\n    \"title\": \"乱数ジェネレータ\"\n  },\n  \"randomPortGenerator\": {\n    \"description\": \"カスタマイズ可能なオプションを使用して、指定された範囲内でランダムなネットワーク ポートを生成します。\",\n    \"error\": {\n      \"generationFailed\": \"ランダムポートの生成に失敗しました。入力パラメータを確認してください。\"\n    },\n    \"info\": {\n      \"description\": \"ランダムポートジェネレータは、指定された範囲内で予測不可能なネットワークポート番号を生成します。このツールはIANAポート番号標準に準拠しており、一般的なサービスの識別機能も備えています。開発、テスト、ネットワーク設定、ポート競合の回避に役立ちます。\",\n      \"title\": \"ランダム ポート ジェネレーターとは何ですか?\"\n    },\n    \"longDescription\": \"指定された範囲（既知、登録済み、動的、またはカスタム）内でランダムなネットワークポートを生成します。開発、テスト、ネットワーク構成に最適です。一般的なポートのポートサービス識別機能も備えています。\",\n    \"options\": {\n      \"generation\": {\n        \"allowDuplicates\": {\n          \"description\": \"同じポートを複数回表示できるようにする\",\n          \"title\": \"重複を許可する\"\n        },\n        \"countDescription\": \"生成するランダムポートの数（1～1,000）\",\n        \"sortResults\": {\n          \"description\": \"生成されたポートを昇順に並べ替える\",\n          \"title\": \"結果を並べ替える\"\n        },\n        \"title\": \"生成オプション\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"生成されたポートを区切る文字\",\n        \"title\": \"出力設定\"\n      },\n      \"range\": {\n        \"custom\": \"カスタム範囲\",\n        \"dynamic\": \"動的ポート (49152-65535)\",\n        \"maxPortDescription\": \"最大ポート番号（1～65535）\",\n        \"minPortDescription\": \"最小ポート番号（1～65535）\",\n        \"registered\": \"登録ポート（1024-49151）\",\n        \"title\": \"ポート範囲設定\",\n        \"wellKnown\": \"よく知られているポート（1～1023）\"\n      }\n    },\n    \"result\": {\n      \"count\": \"カウント\",\n      \"hasDuplicates\": \"重複が含まれています\",\n      \"isSorted\": \"ソート済み\",\n      \"portDetails\": \"ポートの詳細\",\n      \"range\": \"ポート範囲\",\n      \"title\": \"生成されたランダムポート\"\n    },\n    \"shortDescription\": \"ランダムなネットワークポートを生成する\",\n    \"title\": \"ランダムポートジェネレーター\"\n  },\n  \"slackline\": {\n    \"description\": \"スラックラインの張力を計算する\",\n    \"longDescription\": \"この計算機はロープの中央に荷重がかかることを想定しています\",\n    \"shortDescription\": \"スラックラインや物干しロープのおおよその張力を計算しましょう。ただし、安全のためにこれに頼らないでください。\",\n    \"title\": \"スラックラインの張力\"\n  },\n  \"sphereArea\": {\n    \"description\": \"球の面積\",\n    \"longDescription\": \"この計算機は、A = 4πr²という公式を用いて球の表面積を計算します。半径を入力して表面積を求めることも、表面積を入力して必要な半径を計算することもできます。このツールは、幾何学を学ぶ学生、球体を扱うエンジニア、そして球面に関する計算を行う必要があるすべての人にとって便利です。\",\n    \"shortDescription\": \"球の半径に基づいて球の表面積を計算する\",\n    \"title\": \"球の面積\"\n  },\n  \"sphereVolume\": {\n    \"description\": \"球の体積\",\n    \"longDescription\": \"この計算機は、V = (4/3)πr³という公式を用いて球の体積を計算します。半径または直径を入力して体積を求めることも、体積を入力して必要な半径を求めることもできます。このツールは、物理学、工学、製造業などの分野で球体を扱う学生、エンジニア、専門家にとって非常に役立ちます。\",\n    \"shortDescription\": \"半径または直径を使用して球の体積を計算する\",\n    \"title\": \"球の体積\"\n  },\n  \"sum\": {\n    \"description\": \"数値リストの合計を計算します。数値をカンマまたは改行で区切って入力すると、合計が求められます。\",\n    \"example1Description\": \"この例では、10個の正の整数の合計を計算します。これらの整数は列にリストされており、合計は19494になります。\",\n    \"example1Title\": \"10個の正の数の合計\",\n    \"example2Description\": \"この例では、3音節の名詞20個の列を反転し、すべての単語を下から上へ出力します。リスト項目を区切るために、入力項目区切り文字として\\\\n文字を使用しています。つまり、各項目はそれぞれ独立した行に表示されます。\",\n    \"example2Title\": \"公園の木を数える\",\n    \"example3Description\": \"この例では、正の数、負の数、整数、小数など、90種類の異なる値を加算します。入力区切り文字をカンマに設定し、すべての値を加算すると、出力として0が得られます。\",\n    \"example3Title\": \"整数と小数の合計\",\n    \"example4Description\": \"この例では、10桁すべての合計を計算し、「累計を印刷」オプションを有効にします。加算の過程で合計の中間値を取得します。したがって、出力には次の順序が示されます：0、1（0 + 1）、3（0 + 1 + 2）、6（0 + 1 + 2 + 3）、10（0 + 1 + 2 + 3 + 4）、など。\",\n    \"example4Title\": \"数字の累計\",\n    \"extractionTypes\": {\n      \"delimiter\": {\n        \"description\": \"ここで数値区切りをカスタマイズします。(デフォルトでは改行です。)\",\n        \"title\": \"数値区切り文字\"\n      },\n      \"smart\": {\n        \"description\": \"入力内の数字を自動検出します。\",\n        \"title\": \"スマートサム\"\n      }\n    },\n    \"inputTitle\": \"入力\",\n    \"numberExtraction\": \"番号抽出\",\n    \"printRunningSum\": \"累計を印刷\",\n    \"printRunningSumDescription\": \"計算された合計を段階的に表示します。\",\n    \"resultTitle\": \"合計\",\n    \"runningSum\": \"累計\",\n    \"shortDescription\": \"数字の合計を計算する\",\n    \"title\": \"和\",\n    \"toolInfo\": {\n      \"description\": \"これは、複数の数値の合計を計算するためのブラウザベースのオンラインユーティリティです。数値は、カンマ、スペース、または改行を含む任意の文字で区切って入力できます。また、合計したい数値を含むテキストデータを貼り付けるだけで、ユーティリティがそれらの数値を抽出し、合計を計算します。\",\n      \"title\": \"数値合計計算機とは何ですか?\"\n    }\n  },\n  \"voltageDropInWire\": {\n    \"description\": \"2導体ケーブルの往復電圧と電力損失を計算します\",\n    \"longDescription\": \"この計算機は、2芯電気ケーブルにおける電圧降下と電力損失を計算するのに役立ちます。ケーブルの長さ、電線ゲージ（断面積）、材質の抵抗率、電流の流れを考慮します。このツールは、往復の電圧降下、ケーブルの総抵抗、そして熱として消費される電力を計算します。これは、電気技術者、電気技師、そして趣味で電気システムを設計する際に、負荷における電圧レベルが許容範囲内に維持されるようにする際に特に役立ちます。\",\n    \"shortDescription\": \"長さ、材質、電流に基づいて電気ケーブルの電圧降下と電力損失を計算します\",\n    \"title\": \"ケーブルの往復電圧降下\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/pdf.json",
    "content": "{\n  \"compressPdf\": {\n    \"compressedFileSize\": \"圧縮ファイルサイズ\",\n    \"compressingPdf\": \"PDF を圧縮しています...\",\n    \"compressionLevel\": \"圧縮レベル\",\n    \"compressionSettings\": \"圧縮設定\",\n    \"description\": \"Ghostscriptを使用して品質を維持しながらPDFファイルのサイズを縮小する\",\n    \"errorCompressingPdf\": \"PDF の圧縮に失敗しました: {{error}}\",\n    \"errorReadingPdf\": \"PDFファイルの読み取りに失敗しました。有効なPDFであることを確認してください。\",\n    \"fileSize\": \"元のファイルサイズ\",\n    \"highCompression\": \"高圧縮\",\n    \"highCompressionDescription\": \"多少の品質低下を伴いファイルサイズを最大限に削減\",\n    \"inputTitle\": \"入力PDF\",\n    \"longDescription\": \"Ghostscriptを使ってブラウザ内で安全にPDFファイルを圧縮できます。ファイルはデバイスから一切出ないので、完全なプライバシーを確保しながら、メール共有、ウェブサイトへのアップロード、ストレージ容量の節約のためにファイルサイズを縮小できます。WebAssemblyテクノロジーを搭載しています。\",\n    \"lowCompression\": \"低圧縮\",\n    \"lowCompressionDescription\": \"品質の低下を最小限に抑えながらファイルサイズをわずかに削減\",\n    \"mediumCompression\": \"中圧縮\",\n    \"mediumCompressionDescription\": \"ファイルサイズと品質のバランス\",\n    \"pages\": \"ページ数\",\n    \"resultTitle\": \"圧縮PDF\",\n    \"shortDescription\": \"ブラウザでPDFファイルを安全に圧縮\",\n    \"title\": \"PDFを圧縮\"\n  },\n  \"editor\": {\n    \"description\": \"注釈、フォーム入力、ハイライト、エクスポート機能を備えた高度なPDFエディター。テキスト挿入、描画、ハイライト、署名、フォーム入力などのプロ仕様のツールを使って、ブラウザ内で直接PDFを編集できます。\",\n    \"shortDescription\": \"高度な注釈、署名、編集ツールでPDFを編集\",\n    \"title\": \"PDFエディター\"\n  },\n  \"merge\": {\n    \"inputTitle\": \"入力PDF\",\n    \"loadingText\": \"ページの抽出\",\n    \"resultTitle\": \"結合したPDFを出力する\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使えば、複数のPDFファイルを1つの文書に結合することができます。結合したいPDFファイルをアップロードするだけで、入力ファイルのすべてのページが1つのPDF文書に結合されます。\",\n      \"title\": \"Merge PDF ツールの使い方\"\n    }\n  },\n  \"mergePdf\": {\n    \"description\": \"複数の PDF ファイルを 1 つのドキュメントに結合します。\",\n    \"inputTitle\": \"入力PDF\",\n    \"mergingPdfs\": \"PDFの結合\",\n    \"pdfOptions\": \"PDFオプション\",\n    \"resultTitle\": \"結合されたPDF\",\n    \"shortDescription\": \"複数のPDFファイルを1つの文書に結合する\",\n    \"sortByFileName\": \"ファイル名で並べ替え\",\n    \"sortByFileNameDescription\": \"PDFをファイル名のアルファベット順に並べ替える\",\n    \"sortByUploadOrder\": \"アップロード順に並べ替え\",\n    \"sortByUploadOrderDescription\": \"PDFをアップロードされた順序で保存する\",\n    \"title\": \"PDFを結合\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使えば、複数のPDFファイルを1つの文書に結合できます。PDFファイルの並び順を指定すると、ツールが指定した順序で結合します。\",\n      \"title\": \"PDFファイルの結合\"\n    }\n  },\n  \"pdfToEpub\": {\n    \"description\": \"電子書籍リーダーとの互換性を高めるために、PDF ドキュメントを EPUB ファイルに変換します。\",\n    \"shortDescription\": \"PDFファイルをEPUB形式に変換する\",\n    \"title\": \"PDFからEPUBへ\"\n  },\n  \"pdfToPng\": {\n    \"description\": \"PDF ドキュメントを PNG パネルに変換します。\",\n    \"longDescription\": \"PDFをアップロードすると、ブラウザ内で各ページを高画質のPNG画像に変換できます。このツールは、ビジュアルコンテンツの抽出や個々のページの共有に最適です。データはアップロードされず、すべてローカルで実行されます。\",\n    \"shortDescription\": \"PDFをPNG画像に変換する\",\n    \"title\": \"PDFからPNGへ\"\n  },\n  \"convertToPdf\": {\n    \"description\": \"様々な画像形式（PNG、GIF、JPG、TIF、PSD、SVG、WEBP、HEIC、RAW）をPDFに変換します。画像サイズとページの向きを調整できます。\",\n    \"shortDescription\": \"画像を PDF に変換し、画像のサイズやページの向きを調整できます。\",\n    \"title\": \"画像を PDF に変換\"\n  },\n  \"protectPdf\": {\n    \"description\": \"ブラウザで安全にPDFファイルにパスワード保護を追加します\",\n    \"shortDescription\": \"PDFファイルをパスワードで安全に保護する\",\n    \"title\": \"PDFを保護する\"\n  },\n  \"rotatePdf\": {\n    \"allPagesWillBeRotated\": \"全て {{count}} ページが回転します\",\n    \"angleOptions\": {\n      \"clockwise90\": \"90°時計回り\",\n      \"counterClockwise270\": \"270°（反時計回り90°）\",\n      \"upsideDown180\": \"180°（上下逆さま）\"\n    },\n    \"applyToAllPages\": \"すべてのページに適用\",\n    \"description\": \"PDF ドキュメント内のページを回転します。\",\n    \"inputTitle\": \"入力PDF\",\n    \"longDescription\": \"PDFページの向きを90度、180度、または270度回転して変更できます。スキャンミスを修正したり、印刷用のPDFを準備したりする際に便利です。\",\n    \"pageRangesDescription\": \"ページ番号または範囲をコンマで区切って入力してください（例：1,3,5-7）\",\n    \"pageRangesPlaceholder\": \"例：1.5-8\",\n    \"pagesWillBeRotated\": \"{{count}} ページ{{count !== 1 ? 's' : ''}} 回転します\",\n    \"pdfPageCount\": \"PDFには {{count}} ページ{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"回転したPDF\",\n    \"rotatingPages\": \"ページの回転\",\n    \"rotationAngle\": \"回転角度\",\n    \"rotationSettings\": \"回転設定\",\n    \"shortDescription\": \"PDF文書のページを回転する\",\n    \"title\": \"PDFを回転\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使うと、PDF文書内のページを回転できます。すべてのページを回転することも、特定のページを指定して回転させることもできます。回転角度は、時計回り90度、180度（上下反転）、または反時計回り90度（270度）から選択できます。特定のページを回転させるには、「すべてのページに適用」のチェックを外し、ページ番号またはページ範囲をカンマで区切って入力します（例：1,3,5-7）。\",\n      \"title\": \"PDF回転ツールの使い方\"\n    }\n  },\n  \"splitPdf\": {\n    \"description\": \"PDF ドキュメントから特定のページを抽出します。\",\n    \"extractingPages\": \"ページの抽出\",\n    \"inputTitle\": \"入力PDF\",\n    \"pageExtractionPreview\": \"{{count}} ページ{{count !== 1 ? 's' : ''}} 抽出されます\",\n    \"pageRangesDescription\": \"ページ番号または範囲をコンマで区切って入力してください（例：1,3,5-7）\",\n    \"pageRangesPlaceholder\": \"例：1.5-8\",\n    \"pageSelection\": \"ページ選択\",\n    \"pdfPageCount\": \"PDFには {{count}} ページ{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"抽出されたPDF\",\n    \"shortDescription\": \"PDFファイルから特定のページを抽出する\",\n    \"title\": \"PDFを分割\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使うと、PDF文書から特定のページを抽出できます。抽出するページは、個々のページまたはページ範囲で指定できます。\",\n      \"title\": \"PDFを分割\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/string.json",
    "content": "{\n  \"base64\": {\n    \"decode\": \"Base64デコード\",\n    \"description\": \"Base64 エンコーディングを使用してテキストをエンコードまたはデコードします。\",\n    \"encode\": \"Base64エンコード\",\n    \"inputTitle\": \"入力データ\",\n    \"optionsTitle\": \"Base64オプション\",\n    \"resultTitle\": \"結果\",\n    \"shortDescription\": \"Base64 を使用してデータをエンコードまたはデコードします。\",\n    \"title\": \"Base64 エンコーダ/デコーダ\",\n    \"toolInfo\": {\n      \"description\": \"Base64は、ASCII文字列形式のデータを基数64の表現に変換して表現するエンコード方式です。文字列のエンコードにも使用できますが、テキストデータを扱うように設計されたメディアで伝送するバイナリデータのエンコードによく使用されます。\",\n      \"title\": \"Base64 とは何ですか?\"\n    }\n  },\n  \"censor\": {\n    \"description\": \"テキスト内の単語を検閲するためのユーティリティです。左側の入力フォームにテキストを入力し、オプションで不適切な単語をすべて指定すると、出力領域に検閲されたテキストが即座に表示されます。\\\", longDescription: 'このオンラインツールを使えば、あらゆるテキスト内の特定の単語を検閲できます。不要な単語（罵り言葉や秘密の言葉など）のリストを指定すると、プログラムがそれらを代替単語に置き換え、安全に読めるテキストを作成します。単語は、オプションの複数行テキストフィールドに1行につき1単語ずつ入力することで指定できます。', キーワード: ['text', 'censor', 'words', 'characters'], コンポーネント: lazy(() => import('./index')), i18n: { name: 'string:censor.title', description: 'string:censor.description\",\n    \"shortDescription\": \"不適切な単語をすぐにマスクするか、別の単語に置き換えます。\",\n    \"title\": \"テキスト検閲\"\n  },\n  \"createPalindrome\": {\n    \"description\": \"あらゆるテキストから回文を作成できる、世界で最もシンプルなブラウザベースのユーティリティです。テキストを入力すると、前後どちらから読んでも同じ回文に瞬時に変換されます。言葉遊び、対称的なテキストパターンの作成、言語的好奇心の探求などに最適です。\",\n    \"shortDescription\": \"前後どちらから読んでも同じテキストを作成する\",\n    \"title\": \"回文を作成する\"\n  },\n  \"extractSubstring\": {\n    \"description\": \"テキストから部分文字列を抽出できる、世界で最もシンプルなブラウザベースのユーティリティです。テキストを入力し、開始位置と終了位置を指定するだけで、必要な部分を抽出できます。データ処理、テキスト分析、あるいは大きなテキストブロックから特定のコンテンツを抽出するのに最適です。\",\n    \"shortDescription\": \"指定した位置の間のテキストの一部を抽出する\",\n    \"title\": \"部分文字列の抽出\"\n  },\n  \"hiddenCharacterDetector\": {\n    \"analysisOptions\": \"分析オプション\",\n    \"category\": \"カテゴリ\",\n    \"description\": \"隠された Unicode 文字、特に攻撃に使用される可能性のある RTL Override 文字を検出します。\",\n    \"foundChars\": \"見つかった {{count}} 隠し文字:\",\n    \"inputPlaceholder\": \"隠し文字をチェックするテキストを入力してください...\",\n    \"inputTitle\": \"分析するテキスト\",\n    \"invisibleChar\": \"見えないキャラクター\",\n    \"invisibleFound\": \"見えない文字が見つかりました\",\n    \"longDescription\": \"このツールは、テキスト内の隠れたUnicode文字、特に攻撃に利用される可能性のある右から左への（RTL）オーバーライド文字を検出するのに役立ちます。一見無害なテキストに隠れている可能性のある、不可視文字、ゼロ幅文字、その他の潜在的に悪意のあるUnicodeシーケンスを識別できます。\",\n    \"noHiddenChars\": \"テキスト内に隠し文字は検出されませんでした。\",\n    \"optionsDescription\": \"検出する隠し文字の種類と結果の表示方法を設定します。\",\n    \"position\": \"位置\",\n    \"rtlAlert\": \"⚠️ RTL オーバーライド文字が検出されました。このテキストには悪意のある隠し文字が含まれている可能性があります。\",\n    \"rtlFound\": \"RTLオーバーライドが見つかりました\",\n    \"rtlOverride\": \"RTLオーバーライド文字\",\n    \"rtlWarning\": \"警告: RTL オーバーライド文字が検出されました。これは攻撃に使用される可能性があります。\",\n    \"shortDescription\": \"テキスト内の隠れたUnicode文字を見つける\",\n    \"summary\": \"分析概要\",\n    \"title\": \"隠し文字検出\",\n    \"totalChars\": \"隠し文字の合計数: {{count}}\",\n    \"unicode\": \"ユニコード\",\n    \"zeroWidthChar\": \"ゼロ幅文字\",\n    \"zeroWidthFound\": \"ゼロ幅文字が見つかりました\"\n  },\n  \"join\": {\n    \"blankLinesAndTrailingSpaces\": \"空白行と末尾のスペース\",\n    \"deleteBlankDescription\": \"テキスト シンボルのない行を削除します。\",\n    \"deleteBlankTitle\": \"空白行を削除する\",\n    \"deleteTrailingDescription\": \"行末のスペースとタブを削除します。\",\n    \"deleteTrailingTitle\": \"末尾のスペースを削除\",\n    \"description\": \"カスタマイズ可能なセパレーターを使用してテキスト部分を結合します。\",\n    \"inputTitle\": \"テキストピース\",\n    \"joinCharacterDescription\": \"テキストの断片を連結する記号。(デフォルトではスペース)\",\n    \"joinCharacterPlaceholder\": \"キャラクターを参加させる\",\n    \"resultTitle\": \"結合テキスト\",\n    \"shortDescription\": \"指定した区切り文字でテキスト要素を結合する\",\n    \"textMergedOptions\": \"テキスト結合オプション\",\n    \"title\": \"テキストを結合\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使えば、テキストの一部を結合できます。改行で区切られたテキスト値のリストを受け取り、それらを結合します。結合後のテキストの各部分の間に挿入する文字を設定できます。また、すべての空行を無視し、すべての行末のスペースとタブを削除することもできます。Textabulous！\",\n      \"title\": \"テキストジョイナーとは何ですか?\"\n    }\n  },\n  \"palindrome\": {\n    \"description\": \"テキストが回文であるかどうかをチェックする、世界で最もシンプルなブラウザベースのユーティリティです。テキストが前後どちらから読んでも同じかどうかを瞬時に検証します。単語パズル、言語分析、対称的なテキストパターンの検証に最適です。様々な区切り文字と複数単語の回文検出に対応しています。\",\n    \"shortDescription\": \"テキストが前後で同じ読み方をしているか確認する\",\n    \"title\": \"回文\"\n  },\n  \"passwordGenerator\": {\n    \"avoidAmbiguous\": \"あいまいな文字（i、I、l、0、O）は避けてください\",\n    \"description\": \"長さと文字種をカスタマイズ可能な、安全なランダムパスワードを生成します。小文字、大文字、数字、特殊文字から選択できます。読みやすさを向上させるため、あいまいな文字を避けるオプションも用意されています。\",\n    \"includeLowercase\": \"小文字（a～z）を含める\",\n    \"includeNumbers\": \"数字を含める（0～9）\",\n    \"includeSymbols\": \"特殊文字を含める\",\n    \"includeUppercase\": \"大文字（A～Z）を含める\",\n    \"lengthDesc\": \"パスワードの長さ\",\n    \"lengthPlaceholder\": \"例：12\",\n    \"optionsTitle\": \"パスワードオプション\",\n    \"resultTitle\": \"生成されたパスワード\",\n    \"shortDescription\": \"カスタムオプションで安全なランダムパスワードを生成する\",\n    \"title\": \"パスワードジェネレーター\",\n    \"toolInfo\": {\n      \"description\": \"このツールは、選択した条件に基づいて安全なランダムパスワードを生成します。長さをカスタマイズしたり、様々な文字種を含めるか除外したり、読みやすさを向上させるために曖昧な文字を避けたりすることができます。アカウント、アプリケーション、その他あらゆるセキュリティニーズに対応する強力なパスワードの作成に最適です。\",\n      \"title\": \"パスワードジェネレータについて\"\n    }\n  },\n  \"quote\": {\n    \"allowDoubleQuotation\": \"二重引用符を許可する\",\n    \"description\": \"カスタマイズ可能なオプションを使用してテキストを引用符で囲みます。\",\n    \"inputTitle\": \"入力テキスト\",\n    \"leftQuoteDescription\": \"左引用文字\",\n    \"processAsMultiLine\": \"複数行テキストとして処理\",\n    \"quoteEmptyLines\": \"空行を引用する\",\n    \"quoteOptions\": \"引用オプション\",\n    \"resultTitle\": \"引用テキスト\",\n    \"rightQuoteDescription\": \"右引用符文字\",\n    \"shortDescription\": \"さまざまなスタイルでテキストを引用符で囲む\",\n    \"title\": \"テキスト引用\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使うと、テキストを引用符で囲むことができます。様々な引用符文字を選択したり、複数行テキストを扱ったり、空行の処理方法を制御したりできます。プログラミング用のテキストの作成、データの書式設定、スタイリッシュなテキストの作成に役立ちます。\",\n      \"title\": \"テキスト引用\"\n    }\n  },\n  \"randomizeCase\": {\n    \"description\": \"テキストの大文字と小文字をランダム化する、世界で最もシンプルなブラウザベースのユーティリティです。テキストを入力すると、大文字と小文字がランダムにランダムに変換されます。ユニークなテキストエフェクトの作成、大文字と小文字の区別のテスト、多様なテキストパターンの生成に最適です。\",\n    \"shortDescription\": \"テキスト内の大文字と小文字をランダムにする\",\n    \"title\": \"ケースをランダム化\"\n  },\n  \"removeDuplicateLines\": {\n    \"description\": \"左側の入力フォームにテキストを読み込むと、出力エリアに重複行のないテキストが瞬時に表示されます。強力、無料、そして高速。テキスト行の読み込み – 重複行のないテキスト行を取得\",\n    \"shortDescription\": \"テキストから重複行をすべて素早く削除します\",\n    \"title\": \"重複行を削除する\"\n  },\n  \"repeat\": {\n    \"delimiterDescription\": \"出力コピーの区切り文字。\",\n    \"delimiterPlaceholder\": \"デリミタ\",\n    \"description\": \"カスタマイズ可能なセパレーターを使用してテキストを複数回繰り返します。\",\n    \"inputTitle\": \"入力テキスト\",\n    \"numberPlaceholder\": \"番号\",\n    \"repeatAmountDescription\": \"繰り返し回数。\",\n    \"repetitionsDelimiter\": \"繰り返し区切り文字\",\n    \"resultTitle\": \"繰り返しテキスト\",\n    \"shortDescription\": \"テキストを複数回繰り返す\",\n    \"textRepetitions\": \"テキストの繰り返し\",\n    \"title\": \"テキストの繰り返し\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使用すると、オプションの区切り文字を使用して、特定のテキストを複数回繰り返すことができます。\",\n      \"title\": \"テキストの繰り返し\"\n    }\n  },\n  \"reverse\": {\n    \"description\": \"世界で最もシンプルなブラウザベースのテキスト反転ユーティリティ。任意のテキストを入力すると、文字ごとに瞬時に反転します。鏡文字の作成、回文の解析、テキストパターンの操作に最適です。反転時にスペースと特殊文字は保持されます。\",\n    \"inputTitle\": \"反転するテキスト\",\n    \"processMultiLine\": \"複数行テキストを処理する\",\n    \"processMultiLineDescription\": \"各行は独立して反転されます\",\n    \"resultTitle\": \"反転テキスト\",\n    \"reversalOptions\": \"反転オプション\",\n    \"shortDescription\": \"テキストを文字ごとに反転します\",\n    \"skipEmptyLines\": \"空行をスキップする\",\n    \"skipEmptyLinesDescription\": \"出力から空行は削除されます\",\n    \"title\": \"逆行する\",\n    \"trimWhitespace\": \"空白をトリムする\",\n    \"trimWhitespaceDescription\": \"各行の先頭と末尾の空白を削除します\"\n  },\n  \"rot13\": {\n    \"description\": \"ROT13 暗号を使用してテキストをエンコードまたはデコードします。\",\n    \"inputTitle\": \"入力テキスト\",\n    \"resultTitle\": \"ROT13の結果\",\n    \"shortDescription\": \"ROT13 暗号を使用してテキストをエンコードまたはデコードします。\",\n    \"title\": \"ROT13 エンコーダ/デコーダ\",\n    \"toolInfo\": {\n      \"description\": \"ROT13（13桁回転）は、文字をアルファベットの13番目の文字に置き換えるシンプルな文字置換暗号です。ROT13は、古代ローマで開発されたシーザー暗号の特殊なケースです。英語のアルファベットは26文字であるため、ROT13はシーザー暗号の逆であり、つまりROT13を元に戻すには同じアルゴリズムが適用され、エンコードとデコードに同じ操作を使用することができます。\",\n      \"title\": \"ROT13とは何ですか?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"テキスト内の文字を指定された位置だけ回転します。\",\n    \"inputTitle\": \"入力テキスト\",\n    \"processAsMultiLine\": \"複数行テキストとして処理する（各行を個別に回転する）\",\n    \"resultTitle\": \"回転したテキスト\",\n    \"rotateLeft\": \"左に回転\",\n    \"rotateRight\": \"右に回転\",\n    \"rotationOptions\": \"回転オプション\",\n    \"shortDescription\": \"テキスト内の文字を位置によってシフトします。\",\n    \"stepDescription\": \"ローテーションするポジションの数\",\n    \"title\": \"テキストを回転する\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使用すると、文字列内の文字を指定した数だけ回転させることができます。左または右に回転したり、複数行のテキストを各行ごとに回転させて処理したりできます。文字列の回転は、単純なテキスト変換、パターンの作成、基本的な暗号化技術の実装に役立ちます。\",\n      \"title\": \"弦の回転\"\n    }\n  },\n  \"split\": {\n    \"charAfterChunkDescription\": \"各チャンクの後の文字\",\n    \"charBeforeChunkDescription\": \"各チャンクの前の文字\",\n    \"chunksDescription\": \"出力内の同じ長さのチャンクの数。\",\n    \"chunksTitle\": \"チャンクの数を使用する\",\n    \"description\": \"世界で最もシンプルなブラウザベースのテキスト分割ユーティリティです。テキストを入力し、区切り文字を指定するだけで複数の部分に分割できます。データ処理、テキスト操作、または大きなテキストブロックから特定のコンテンツを抽出するのに最適です。\",\n    \"lengthDescription\": \"各出力チャンクに配置されるシンボルの数。\",\n    \"lengthTitle\": \"長さを使って分割する\",\n    \"outputSeparatorDescription\": \"分割されたチャンクの間に挿入される文字。(デフォルトでは改行文字 \\\"\\\\n\\\" です。)\",\n    \"outputSeparatorOptions\": \"出力セパレーターオプション\",\n    \"regexDescription\": \"テキストを複数の部分に分割するために使用される正規表現。\\n(デフォルトでは複数のスペース)\",\n    \"regexTitle\": \"正規表現を使用して分割する\",\n    \"resultTitle\": \"テキスト部分\",\n    \"shortDescription\": \"セパレータを使用してテキストを複数の部分に分割する\",\n    \"splitSeparatorOptions\": \"分割区切りオプション\",\n    \"symbolDescription\": \"テキストを分割するために使用される文字。\\n(デフォルトはスペース)\",\n    \"symbolTitle\": \"分割にシンボルを使用する\",\n    \"title\": \"スプリット\"\n  },\n  \"statistic\": {\n    \"characterFrequencyAnalysis\": \"文字頻度分析\",\n    \"characterFrequencyAnalysisDescription\": \"各文字がテキストに何回出現するかを数える\",\n    \"delimitersOptions\": \"区切り文字オプション\",\n    \"description\": \"テキストを分析し、包括的な統計を生成します。\",\n    \"includeEmptyLines\": \"空行を含める\",\n    \"includeEmptyLinesDescription\": \"行数を数えるときに空白行を含める\",\n    \"inputTitle\": \"入力テキスト\",\n    \"resultTitle\": \"テキスト統計\",\n    \"sentenceDelimitersDescription\": \"あなたの言語で文を区切るために使用するカスタム文字（コンマで区切る）を入力するか、デフォルトの場合は空白のままにします。\",\n    \"sentenceDelimitersPlaceholder\": \"例: .、!、?、...\",\n    \"shortDescription\": \"テキストに関する統計情報を取得する\",\n    \"statisticsOptions\": \"統計オプション\",\n    \"title\": \"テキスト統計\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使用すると、テキストを分析し、文字数、単語数、行数、文字と単語の頻度分析などの包括的な統計を生成できます。\",\n      \"title\": \"何ですか {{title}}？\"\n    },\n    \"wordDelimitersDescription\": \"単語数をカウントするためのカスタム正規表現を入力するか、デフォルトの場合は空白のままにします。\",\n    \"wordDelimitersPlaceholder\": \"例: \\\\s.,;:!?\\\"«»()…\",\n    \"wordFrequencyAnalysis\": \"単語頻度分析\",\n    \"wordFrequencyAnalysisDescription\": \"各単語がテキストに何回出現するかを数える\"\n  },\n  \"textReplacer\": {\n    \"description\": \"テキスト パターンを新しいコンテンツに置き換えます。\",\n    \"findPatternInText\": \"テキスト内のこのパターンを見つける\",\n    \"findPatternUsingRegexp\": \"正規表現を使ってパターンを見つける\",\n    \"inputTitle\": \"置換するテキスト\",\n    \"newTextPlaceholder\": \"新しいテキスト\",\n    \"regexpDescription\": \"置換する正規表現を入力します。\",\n    \"replacePatternDescription\": \"置換に使用するパターンを入力します。\",\n    \"replaceText\": \"テキストの置換\",\n    \"resultTitle\": \"置換テキスト\",\n    \"searchPatternDescription\": \"置換するテキスト パターンを入力します。\",\n    \"searchText\": \"検索テキスト\",\n    \"shortDescription\": \"コンテンツ内のテキストを素早く置き換える\",\n    \"title\": \"テキスト置換\",\n    \"toolInfo\": {\n      \"description\": \"このシンプルなブラウザベースのツールを使えば、コンテンツ内の特定のテキストを簡単に置き換えることができます。テキストを入力し、置き換えたいテキストと置換値を設定するだけで、すぐに更新されたバージョンを取得できます。\",\n      \"title\": \"テキスト置換\"\n    }\n  },\n  \"toMorse\": {\n    \"dashSymbolDescription\": \"モールス信号のダッシュに対応する記号。\",\n    \"description\": \"テキストをモールス信号に変換します。\",\n    \"dotSymbolDescription\": \"モールス信号のドットに対応する記号。\",\n    \"longSignal\": \"ロングシグナル\",\n    \"resultTitle\": \"モールス信号\",\n    \"shortDescription\": \"テキストをモールス信号に素早くエンコード\",\n    \"shortSignal\": \"ショートシグナル\",\n    \"title\": \"モールス信号への文字列\"\n  },\n  \"truncate\": {\n    \"addTruncationIndicator\": \"切り捨てインジケーターを追加\",\n    \"charactersPlaceholder\": \"キャラクター\",\n    \"description\": \"テキストを指定された長さに短縮します。\",\n    \"indicatorDescription\": \"テキストの末尾（または先頭）に追加する文字。注: 長さにカウントされます。\",\n    \"inputTitle\": \"入力テキスト\",\n    \"leftSideDescription\": \"テキストの先頭から文字を削除します。\",\n    \"leftSideTruncation\": \"左側切り捨て\",\n    \"lengthAndLines\": \"長さと線\",\n    \"lineByLineDescription\": \"各行を個別に切り捨てます。\",\n    \"lineByLineTruncating\": \"行ごとの切り捨て\",\n    \"maxLengthDescription\": \"テキストに残す文字数。\",\n    \"numberPlaceholder\": \"番号\",\n    \"resultTitle\": \"切り捨てられたテキスト\",\n    \"rightSideDescription\": \"テキストの末尾から文字を削除します。\",\n    \"rightSideTruncation\": \"右側切り捨て\",\n    \"shortDescription\": \"指定された長さにテキストを切り捨てる\",\n    \"suffixAndAffix\": \"接尾辞と接辞\",\n    \"title\": \"テキストを切り捨てる\",\n    \"toolInfo\": {\n      \"description\": \"左側の入力フォームにテキストを読み込むと、右側に切り捨てられたテキストが自動的に表示されます。\",\n      \"title\": \"テキストを切り捨てる\"\n    },\n    \"truncationSide\": \"切り捨て側\"\n  },\n  \"uppercase\": {\n    \"description\": \"テキストを大文字に変換します。\",\n    \"inputTitle\": \"入力テキスト\",\n    \"resultTitle\": \"大文字のテキスト\",\n    \"shortDescription\": \"テキストを大文字に変換する\",\n    \"title\": \"大文字に変換\"\n  },\n  \"urlDecode\": {\n    \"inputTitle\": \"入力文字列（URLエスケープ）\",\n    \"resultTitle\": \"出力文字列\",\n    \"toolInfo\": {\n      \"description\": \"文字列を読み込むと、自動的に URL エスケープが解除されます。\",\n      \"longDescription\": \"このツールは、URLエンコードされた文字列をURLデコードします。URLデコードはURLエンコードの逆の操作です。パーセントエンコードされた文字はすべて、ユーザーが理解できる文字にデコードされます。よく知られているパーセントエンコード値としては、スペースの場合は%20、コロンの場合は%3a、スラッシュの場合は%2f、疑問符の場合は%3fなどがあります。パーセント記号の後の2桁の数字は、文字の16進数による文字コード値です。\",\n      \"shortDescription\": \"文字列をすばやく URL アンエスケープします。\",\n      \"title\": \"文字列URLデコーダー\"\n    }\n  },\n  \"urlEncode\": {\n    \"encodingOption\": {\n      \"nonSpecialCharDescription\": \"選択すると、入力文字列内のすべての文字が URL エンコードに変換されます (特殊な文字だけでなく)。\",\n      \"nonSpecialCharPlaceholder\": \"特殊文字以外の文字をエンコードする\",\n      \"title\": \"エンコードオプション\"\n    },\n    \"inputTitle\": \"入力文字列\",\n    \"resultTitle\": \"URLエスケープ文字列\",\n    \"toolInfo\": {\n      \"description\": \"文字列を読み込むと、自動的に URL エスケープされます。\",\n      \"longDescription\": \"このツールは文字列をURLエンコードします。URLの特殊文字はパーセント記号エンコードに変換されます。このエンコードは、各文字の数値がパーセント記号とそれに続く2桁の16進数値に変換されるため、パーセントエンコードと呼ばれます。16進数値は文字のコードポイント値に基づいて決定されます。例えば、スペースは%20、コロンは%3a、スラッシュは%2fにエスケープされます。特殊文字以外の文字は変更されません。特殊文字以外の文字もパーセントエンコードに変換する必要がある場合は、そのための追加オプションも追加しました。この動作を有効にするには、encode-non-special-charsオプションを選択してください。\",\n      \"shortDescription\": \"文字列をすばやく URL エスケープします。\",\n      \"title\": \"文字列URLエンコーダ\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/time.json",
    "content": "{\n  \"checkLeapYears\": {\n    \"description\": \"その年がうるう年かどうかを確認し、うるう年情報を取得します。\",\n    \"exampleDescription\": \"友人の一人は閏年の2月29日に生まれたため、誕生日は4年に一度しかありません。彼女の誕生日がいつなのかはなかなか思い出せないので、プログラムを使って、今後の閏年のリマインダーリストを作成しています。彼女の次の誕生日のリストを作成するには、2025年から2040年までの一連の年を入力に読み込み、各年のステータスを取得します。プログラムが閏年だと判断した場合、2月29日の誕生日パーティーに招待されることがわかります。\",\n    \"exampleTitle\": \"2月29日の誕生日を検索\",\n    \"inputTitle\": \"入力年\",\n    \"resultTitle\": \"閏年の結果\",\n    \"shortDescription\": \"その年がうるう年かどうかをチェックする\",\n    \"title\": \"うるう年を確認する\",\n    \"toolInfo\": {\n      \"description\": \"閏年とは、暦年を天文年と一致させるために、1日（2月29日）が追加される年です。閏年は4年に1度発生しますが、100で割り切れるが400で割り切れない年は例外です。\",\n      \"title\": \"うるう年とは何ですか?\"\n    }\n  },\n  \"convertDaysToHours\": {\n    \"addHoursName\": \"営業時間名を追加\",\n    \"addHoursNameDescription\": \"出力値に文字列「時間」を追加します\",\n    \"description\": \"カスタマイズ可能なオプションを使用して日数を時間数に変換します。\",\n    \"hoursName\": \"時間名\",\n    \"shortDescription\": \"日数を時間数に変換する\",\n    \"title\": \"日数を時間数に変換する\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使うと、日数を時間数に変換できます。日数を数値または単位付きで入力すると、ツールが時間数に変換します。また、出力値の末尾に「時間」を付けることもできます。\",\n      \"title\": \"日数を時間数に変換する\"\n    }\n  },\n  \"convertHoursToDays\": {\n    \"addDaysName\": \"曜日名を追加\",\n    \"addDaysNameDescription\": \"出力値に文字列 days を追加します\",\n    \"daysName\": \"日付名\",\n    \"description\": \"カスタマイズ可能なオプションを使用して時間を日数に変換します。\",\n    \"shortDescription\": \"時間を日数に変換する\",\n    \"title\": \"時間を日数に変換する\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使うと、時間を日数に変換できます。時間を数値または単位付きで入力すると、ツールが日数に変換します。また、出力値の末尾に「日数」を付けることもできます。\",\n      \"title\": \"時間を日数に変換する\"\n    }\n  },\n  \"convertSecondsToTime\": {\n    \"addPadding\": \"パディングを追加\",\n    \"addPaddingDescription\": \"時間、分、秒にゼロパディングを追加します。\",\n    \"description\": \"秒数を判読可能な時刻形式（時:分:秒）に変換します。秒数を入力すると、フォーマットされた時刻が表示されます。\",\n    \"shortDescription\": \"秒を時間形式に変換する\",\n    \"timePadding\": \"時間のパディング\",\n    \"title\": \"秒を時間に変換する\",\n    \"toolInfo\": {\n      \"title\": \"何ですか {{title}}？\"\n    }\n  },\n  \"convertTimeToSeconds\": {\n    \"description\": \"フォーマットされた時間 (HH:MM:SS) を秒に変換します。\",\n    \"inputTitle\": \"入力時間\",\n    \"resultTitle\": \"秒\",\n    \"shortDescription\": \"時間形式を秒に変換する\",\n    \"title\": \"時間を秒に変換する\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使うと、フォーマットされた時刻文字列（HH:MM:SS）を秒数に変換できます。期間や時間間隔を計算するのに便利です。\",\n      \"title\": \"時間を秒に変換する\"\n    }\n  },\n  \"convertUnixToDate\": {\n    \"addUtcLabel\": \"「UTC」サフィックスを追加\",\n    \"addUtcLabelDescription\": \"変換された日付の後に「UTC」を表示します（UTCモードのみ）\",\n    \"description\": \"Unix タイムスタンプを人間が読める日付に変換します。\",\n    \"outputOptions\": \"出力オプション\",\n    \"shortDescription\": \"Unixタイムスタンプを日付に変換する\",\n    \"title\": \"Unix を日付に変換する\",\n    \"toolInfo\": {\n      \"description\": \"このツールは、Unixタイムスタンプ（秒単位）を人間が判読できる日付形式（例：YYYY-MM-DD HH:MM:SS）に変換します。ローカルタイムスタンプとUTCタイムスタンプの両方の出力をサポートしているため、ログ、API、またはUnixタイムを使用するシステムからのタイムスタンプを素早く解釈するのに役立ちます。\",\n      \"title\": \"Unix を日付に変換する\"\n    },\n    \"useLocalTime\": \"現地時間を使用する\",\n    \"useLocalTimeDescription\": \"UTC ではなくローカルタイムゾーンで変換された日付を表示します\",\n    \"withLabel\": \"オプション\"\n  },\n  \"crontabGuru\": {\n    \"description\": \"cron式を生成し、理解します。自動化されたタスクやシステムジョブのcronスケジュールを作成します。\",\n    \"shortDescription\": \"cron式を生成して理解する\",\n    \"title\": \"クロンタブの達人\"\n  },\n  \"timeBetweenDates\": {\n    \"description\": \"2つの日付間の時差を計算します。正確な日数、時間、分、秒を取得します。\",\n    \"endDate\": \"終了日\",\n    \"endDateTime\": \"終了日時\",\n    \"endTime\": \"終了時間\",\n    \"endTimezone\": \"終了タイムゾーン\",\n    \"shortDescription\": \"2つの日付間の時間を計算する\",\n    \"startDate\": \"開始日\",\n    \"startDateTime\": \"開始日時\",\n    \"startTime\": \"開始時間\",\n    \"startTimezone\": \"開始タイムゾーン\",\n    \"title\": \"日付間の時間\",\n    \"toolInfo\": {\n      \"description\": \"2つの日付と時刻の正確な時差を計算します。複数のタイムゾーンに対応しています。このツールは、時差を様々な単位（年、月、日、時、分、秒）で詳細に表示します。\",\n      \"title\": \"日付間の時間計算機\"\n    }\n  },\n  \"truncateClockTime\": {\n    \"description\": \"秒数または分数を切り捨てて時計の時刻を表示します。時間を最も近い時間、分、またはカスタム間隔に丸めます。\",\n    \"printDroppedComponents\": \"ドロップされたコンポーネントを印刷する\",\n    \"shortDescription\": \"指定された精度でクロック時間を切り捨てる\",\n    \"timePadding\": \"時間のパディング\",\n    \"title\": \"時計時間を切り捨てる\",\n    \"toolInfo\": {\n      \"title\": \"何ですか {{title}}？\"\n    },\n    \"truncateMinutesAndSeconds\": \"分と秒を切り捨てる\",\n    \"truncateMinutesAndSecondsDescription\": \"各クロック時間から分と秒の両方の要素を削除します。\",\n    \"truncateOnlySeconds\": \"秒のみ切り捨て\",\n    \"truncateOnlySecondsDescription\": \"各クロック時間から秒の要素を削除します。\",\n    \"truncationSide\": \"切り捨て側\",\n    \"useZeroPadding\": \"ゼロパディングを使用する\",\n    \"zeroPaddingDescription\": \"すべての時間コンポーネントを常に 2 桁の幅にします。\",\n    \"zeroPrintDescription\": \"ドロップされた部分をゼロ値「00」として表示します。\",\n    \"zeroPrintTruncatedParts\": \"ゼロ印刷切り捨て部分\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/translation.json",
    "content": "{\n  \"audio\": {\n    \"changeSpeed\": {\n      \"description\": \"オーディオファイルの再生速度を変更します。ピッチを維持しながら、オーディオの再生速度を上げたり下げたりできます。\",\n      \"name\": \"オーディオ速度を変更する\",\n      \"shortDescription\": \"オーディオファイルの速度を変更する\"\n    },\n    \"extractAudio\": {\n      \"description\": \"ビデオ ファイルからオーディオ トラックを抽出し、選択した形式 (AAC、MP3、WAV) で個別のオーディオ ファイルとして保存します。\",\n      \"name\": \"音声を抽出する\",\n      \"shortDescription\": \"ビデオ ファイル (MP4、MOV など) からオーディオを AAC、MP3、または WAV に抽出します。\"\n    }\n  },\n  \"baseFileInput\": {\n    \"copyFailed\": \"コピーに失敗しました: {{error}}\",\n    \"dropFileHere\": \"ドロップ {{type}} ここ\",\n    \"fileCopied\": \"ファイルをコピーしました\",\n    \"selectFileDescription\": \"選択するにはここをクリックしてください {{type}} デバイスからCtrl+Vを押して {{type}} クリップボードから、またはデスクトップからファイルをドラッグアンドドロップします\"\n  },\n  \"categories\": {\n    \"audio\": {\n      \"description\": \"オーディオを操作するためのツール – ビデオからオーディオを抽出したり、オーディオ速度を調整したり、複数のオーディオ ファイルを結合したり、その他さまざまな機能を備えています。\",\n      \"title\": \"オーディオツール\"\n    },\n    \"csv\": {\n      \"description\": \"CSV ファイルの操作用ツール - CSV をさまざまな形式に変換し、CSV データを操作し、CSV 構造を検証し、CSV ファイルを効率的に処理します。\",\n      \"title\": \"CSVツール\"\n    },\n    \"gif\": {\n      \"description\": \"GIF アニメーションを操作するためのツール – 透明な GIF の作成、GIF フレームの抽出、GIF へのテキストの追加、GIF の切り取り、回転、反転など。\",\n      \"title\": \"GIFツール\"\n    },\n    \"image-generic\": {\n      \"description\": \"画像を操作するツール – 圧縮、サイズ変更、切り取り、JPG への変換、回転、背景の削除など。\",\n      \"title\": \"画像ツール\"\n    },\n    \"json\": {\n      \"description\": \"JSON データ構造を操作するためのツール – JSON オブジェクトの整形と縮小、JSON 配列のフラット化、JSON 値の文字列化、データの分析など\",\n      \"title\": \"JSONツール\"\n    },\n    \"list\": {\n      \"description\": \"リストを操作するためのツール – リストの並べ替え、反転、ランダム化、一意のリスト項目と重複するリスト項目の検索、リスト項目の区切りの変更など。\",\n      \"title\": \"リストツール\"\n    },\n    \"number\": {\n      \"description\": \"数字を操作するためのツール – 数列を生成したり、数字を単語に変換したり、単語を数字に変換したり、数字を並べ替えたり、四捨五入したり、因数分解したり、その他さまざまな機能があります。\",\n      \"title\": \"数値ツール\"\n    },\n    \"pdf\": {\n      \"description\": \"PDF ファイルの操作ツール - PDF からテキストを抽出したり、PDF を他の形式に変換したり、PDF を操作したり、その他さまざまな機能を備えています。\",\n      \"title\": \"PDFツール\"\n    },\n    \"png\": {\n      \"description\": \"PNG 画像を操作するツール – PNG を JPG に変換したり、透明な PNG を作成したり、PNG の色を変更したり、PNG を切り取ったり、回転したり、サイズ変更したり、その他多くの機能があります。\",\n      \"title\": \"PNGツール\"\n    },\n    \"seeAll\": \"すべてを見る {{title}}\",\n    \"string\": {\n      \"description\": \"テキストを操作するためのツール – テキストを画像に変換したり、テキストを検索して置換したり、テキストを断片に分割したり、テキスト行を結合したり、テキストを繰り返したり、その他さまざまな機能があります。\",\n      \"title\": \"テキストツール\"\n    },\n    \"time\": {\n      \"description\": \"時間と日付を操作するためのツール – 時差の計算、タイムゾーンの変換、日付の書式設定、日付シーケンスの生成など。\",\n      \"title\": \"時間ツール\"\n    },\n    \"try\": \"試す {{title}}\",\n    \"video\": {\n      \"description\": \"ビデオを操作するためのツール – ビデオからフレームを抽出したり、ビデオから GIF を作成したり、ビデオをさまざまな形式に変換したり、その他さまざまな機能を備えています。\",\n      \"title\": \"ビデオツール\"\n    },\n    \"xml\": {\n      \"description\": \"XML データ構造を操作するためのツール - ビューア、ビューティファイア、バリデータなど\",\n      \"title\": \"XMLツール\"\n    }\n  },\n  \"csv\": {\n    \"findIncompleteCsvRecords\": {\n      \"description\": \"以下のフォームにCSVファイルをアップロードするだけで、このツールは行または列に欠損値がないかどうかを自動的にチェックします。ツールのオプションでは、入力ファイルの形式（区切り文字、引用符、コメント文字の指定）を調整できます。さらに、空値のチェックを有効にしたり、空行をスキップしたり、出力時のエラーメッセージの数に制限を設定したりすることもできます。\",\n      \"name\": \"不完全なCSVレコードを見つける\",\n      \"shortDescription\": \"CSV 内で値が欠落している行と列をすばやく見つけます。\"\n    }\n  },\n  \"hero\": {\n    \"brand\": \"オムニツール\",\n    \"description\": \"作業を素早く完了するための究極のツールキット、OmniTools で生産性を飛躍的に向上させましょう。画像、テキスト、リスト、データを編集するための数千もの使いやすいユーティリティに、ブラウザから直接アクセスできます。\",\n    \"examples\": {\n      \"calculateNumberSum\": \"数値の合計を計算する\",\n      \"changeGifSpeed\": \"GIFの速度を変更する\",\n      \"compressPng\": \"PNGを圧縮\",\n      \"createTransparentImage\": \"透明な画像を作成する\",\n      \"prettifyJson\": \"JSONを美しくする\",\n      \"sortList\": \"リストを並べ替える\",\n      \"splitPdf\": \"PDFを分割\",\n      \"splitText\": \"テキストを分割する\",\n      \"trimVideo\": \"ビデオをトリミングする\"\n    },\n    \"searchPlaceholder\": \"すべてのツールを検索\",\n    \"title\": \"物事を素早く終わらせる\"\n  },\n  \"inputFooter\": {\n    \"clear\": \"クリア\",\n    \"copyToClipboard\": \"クリップボードにコピー\",\n    \"importFromFile\": \"ファイルからインポート\"\n  },\n  \"list\": {\n    \"group\": {\n      \"description\": \"リスト項目をグループ化するための、世界で最もシンプルなブラウザベースのユーティリティです。リストを入力し、グループ化の条件を指定するだけで、項目を論理的なグループに整理できます。データの分類、情報の整理、構造化されたリストの作成に最適です。カスタムセパレーターと様々なグループ化オプションをサポートしています。\",\n      \"name\": \"グループ\",\n      \"shortDescription\": \"共通のプロパティでリスト項目をグループ化する\"\n    },\n    \"reverse\": {\n      \"description\": \"これは、すべてのリスト項目を逆順に表示する、非常にシンプルなブラウザベースのアプリケーションです。入力項目は任意の記号で区切ることができ、逆順に表示するリスト項目の区切り文字を変更することもできます。\",\n      \"name\": \"逆行する\",\n      \"shortDescription\": \"リストを素早く逆順にする\"\n    },\n    \"sort\": {\n      \"description\": \"これは、リスト内の項目を昇順または降順に並べ替える、非常にシンプルなブラウザベースのアプリケーションです。項目はアルファベット順、数字順、または長さで並べ替えることができます。また、重複項目や空項目を削除したり、空白で囲まれた項目を個別に切り取ったりすることも可能です。入力リスト項目は、任意の区切り文字、または正規表現を使用して区切ることができます。さらに、ソートされた出力リスト用に新しい区切り文字を作成することもできます。\",\n      \"name\": \"選別\",\n      \"shortDescription\": \"リストを素早く並べ替える\"\n    }\n  },\n  \"navbar\": {\n    \"buyMeACoffee\": \"コーヒーを買ってください\",\n    \"hireMe\": \"私を雇ってください\",\n    \"home\": \"家\",\n    \"tools\": \"ツール\"\n  },\n  \"number\": {\n    \"generate\": {\n      \"description\": \"ブラウザで整数のリストを素早く計算します。リストを取得するには、最初の整数を指定し、以下のオプションで値と合計数を変更するだけで、このユーティリティがその数の整数を生成します。\",\n      \"name\": \"数字を生成する\",\n      \"shortDescription\": \"ブラウザで整数のリストを素早く計算する\"\n    },\n    \"sum\": {\n      \"description\": \"これは、数値を合計する非常にシンプルなブラウザベースのアプリケーションです。入力した数値は任意の記号で区切ることができ、合計した数値の区切り文字を変更することもできます。\",\n      \"name\": \"数字を合計する\",\n      \"shortDescription\": \"数値リストを素早く合計する\"\n    }\n  },\n  \"numericInputWithUnit\": {\n    \"unit\": \"ユニット\"\n  },\n  \"pdf\": {\n    \"compressPdf\": {\n      \"description\": \"Ghostscriptを使用して品質を維持しながらPDFファイルのサイズを縮小する\",\n      \"name\": \"PDFを圧縮\",\n      \"shortDescription\": \"ブラウザでPDFファイルを安全に圧縮\"\n    },\n    \"mergePdf\": {\n      \"description\": \"複数の PDF ファイルを 1 つのドキュメントに結合します。\",\n      \"name\": \"PDFを結合\",\n      \"shortDescription\": \"複数のPDFファイルを1つの文書に結合する\"\n    },\n    \"pdfToEpub\": {\n      \"description\": \"電子書籍リーダーとの互換性を高めるために、PDF ドキュメントを EPUB ファイルに変換します。\",\n      \"name\": \"PDFからEPUBへ\",\n      \"shortDescription\": \"PDFファイルをEPUB形式に変換する\"\n    },\n    \"protectPdf\": {\n      \"description\": \"ブラウザで安全にPDFファイルにパスワード保護を追加します\",\n      \"name\": \"PDFを保護する\",\n      \"shortDescription\": \"PDFファイルをパスワードで安全に保護する\"\n    },\n    \"splitPdf\": {\n      \"description\": \"ページ番号または範囲（例：1,5-8）を使用して、PDF ファイルから特定のページを抽出します。\",\n      \"name\": \"PDFを分割\",\n      \"shortDescription\": \"PDFファイルから特定のページを抽出する\"\n    }\n  },\n  \"resultFooter\": {\n    \"copy\": \"クリップボードにコピー\",\n    \"download\": \"ダウンロード\"\n  },\n  \"string\": {\n    \"createPalindrome\": {\n      \"description\": \"あらゆるテキストから回文を作成できる、世界で最もシンプルなブラウザベースのユーティリティです。テキストを入力すると、前後どちらから読んでも同じ回文に瞬時に変換されます。言葉遊び、対称的なテキストパターンの作成、言語的好奇心の探求などに最適です。\",\n      \"name\": \"回文を作成する\",\n      \"shortDescription\": \"前後どちらから読んでも同じテキストを作成する\"\n    },\n    \"palindrome\": {\n      \"description\": \"テキストが回文であるかどうかをチェックする、世界で最もシンプルなブラウザベースのユーティリティです。テキストが前後どちらから読んでも同じかどうかを瞬時に検証します。単語パズル、言語分析、対称的なテキストパターンの検証に最適です。様々な区切り文字と複数単語の回文検出に対応しています。\",\n      \"name\": \"回文\",\n      \"shortDescription\": \"テキストが前後で同じ読み方をしているか確認する\"\n    },\n    \"repeat\": {\n      \"description\": \"このツールを使用すると、オプションの区切り文字を使用して、特定のテキストを複数回繰り返すことができます。\",\n      \"name\": \"テキストの繰り返し\",\n      \"shortDescription\": \"テキストを複数回繰り返す\"\n    },\n    \"reverse\": {\n      \"description\": \"世界で最もシンプルなブラウザベースのテキスト反転ユーティリティ。任意のテキストを入力すると、文字ごとに瞬時に反転します。鏡文字の作成、回文の解析、テキストパターンの操作に最適です。反転時にスペースと特殊文字は保持されます。\",\n      \"name\": \"逆行する\",\n      \"shortDescription\": \"テキストを文字ごとに反転します\"\n    },\n    \"toMorse\": {\n      \"description\": \"テキストをモールス信号に変換する、世界で最もシンプルなブラウザベースのユーティリティです。左側の入力フォームにテキストを入力すると、出力エリアにモールス信号が瞬時に表示されます。高機能、無料、そして高速。テキストを読み込んでモールス信号を取得。\",\n      \"name\": \"モールス信号への文字列\",\n      \"shortDescription\": \"テキストをモールス信号に素早くエンコード\"\n    },\n    \"uppercase\": {\n      \"description\": \"世界で最もシンプルなブラウザベースのテキスト大文字変換ユーティリティ。テキストを入力するだけで、自動的にすべて大文字に変換されます。見出しの作成、テキストの強調、テキスト形式の標準化に最適です。様々なテキスト形式をサポートし、特殊文字も保持します。\",\n      \"name\": \"大文字\",\n      \"shortDescription\": \"テキストを大文字に変換する\"\n    }\n  },\n  \"toolExamples\": {\n    \"subtitle\": \"クリックしてお試しください!\",\n    \"title\": \"{{title}} 例\"\n  },\n  \"toolFileResult\": {\n    \"copied\": \"ファイルをコピーしました\",\n    \"copyFailed\": \"コピーに失敗しました: {{error}}\",\n    \"loading\": \"読み込み中... 少々時間がかかる場合があります。\",\n    \"result\": \"結果\"\n  },\n  \"toolHeader\": {\n    \"seeExamples\": \"例を見る\"\n  },\n  \"toolLayout\": {\n    \"allToolsTitle\": \"全て {{type}}\"\n  },\n  \"toolMultiFileResult\": {\n    \"copied\": \"ファイルをコピーしました\",\n    \"copyFailed\": \"コピーに失敗しました: {{error}}\",\n    \"loading\": \"読み込み中... 少々時間がかかる場合があります。\",\n    \"result\": \"結果\"\n  },\n  \"toolMultipleAudioInput\": {\n    \"inputTitle\": \"入力 {{type}}\",\n    \"noFilesSelected\": \"ファイルが選択されていません\"\n  },\n  \"toolMultiplePdfInput\": {\n    \"inputTitle\": \"入力 {{type}}\",\n    \"noFilesSelected\": \"ファイルが選択されていません\"\n  },\n  \"toolOptions\": {\n    \"title\": \"ツールオプション\"\n  },\n  \"toolTextInput\": {\n    \"copied\": \"テキストをコピーしました\",\n    \"copyFailed\": \"コピーに失敗しました: {{error}}\",\n    \"input\": \"入力テキスト\",\n    \"placeholder\": \"ここにテキストを入力してください...\"\n  },\n  \"toolTextResult\": {\n    \"copied\": \"テキストをコピーしました\",\n    \"copyFailed\": \"コピーに失敗しました: {{error}}\",\n    \"loading\": \"読み込み中... 少々時間がかかる場合があります。\",\n    \"result\": \"結果\"\n  },\n  \"userTypes\": {\n    \"developers\": \"開発者\",\n    \"generalUsers\": \"一般ユーザー\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/video.json",
    "content": "{\n  \"changeSpeed\": {\n    \"defaultMultiplier\": \"デフォルトの乗数: 2は2倍速を意味します\",\n    \"description\": \"動画ファイルの再生速度を変更できます。音声の同期を維持しながら、動画の再生速度を上げたり下げたりできます。様々な速度調整機能と一般的な動画形式に対応しています。\",\n    \"inputTitle\": \"入力ビデオ\",\n    \"newVideoSpeed\": \"新しいビデオ速度\",\n    \"resultTitle\": \"編集されたビデオ\",\n    \"settingSpeed\": \"速度設定\",\n    \"shortDescription\": \"ビデオの再生速度を変更する\",\n    \"title\": \"ビデオ速度の変更\",\n    \"toolInfo\": {\n      \"title\": \"何ですか {{title}}？\"\n    }\n  },\n  \"compress\": {\n    \"default\": \"デフォルト\",\n    \"description\": \"動画を240p、480p、720pなどの様々な解像度にスケーリングして圧縮します。このツールは、許容できる品質を維持しながらファイルサイズを縮小するのに役立ちます。MP4、WebM、OGGなどの一般的な動画形式をサポートしています。\",\n    \"inputTitle\": \"入力ビデオ\",\n    \"loadingText\": \"ビデオを圧縮しています...\",\n    \"lossless\": \"ロスレス\",\n    \"quality\": \"品質（CRF）\",\n    \"resolution\": \"解決\",\n    \"resultTitle\": \"圧縮ビデオ\",\n    \"shortDescription\": \"さまざまな解像度に合わせてビデオを圧縮する\",\n    \"title\": \"ビデオを圧縮する\",\n    \"worst\": \"最悪\"\n  },\n  \"cropVideo\": {\n    \"cropCoordinates\": \"作物の座標\",\n    \"croppingVideo\": \"ビデオの切り取り\",\n    \"description\": \"ビデオをトリミングして不要な部分を削除します。\",\n    \"errorBeyondHeight\": \"切り取り領域がビデオの高さを超えて拡張されます（{{height}}ピクセル)\",\n    \"errorBeyondWidth\": \"切り取り領域がビデオの幅を超えています（{{width}}ピクセル)\",\n    \"errorCroppingVideo\": \"ビデオのトリミング中にエラーが発生しました。パラメータとビデオファイルを確認してください。\",\n    \"errorLoadingDimensions\": \"ビデオのサイズを読み込めませんでした\",\n    \"errorNonNegativeCoordinates\": \"X座標とY座標は負でない必要があります\",\n    \"errorPositiveDimensions\": \"幅と高さは正の数でなければなりません\",\n    \"height\": \"身長\",\n    \"inputTitle\": \"入力ビデオ\",\n    \"loadVideoForDimensions\": \"ビデオをロードして寸法を確認してください\",\n    \"longDescription\": \"このツールを使えば、動画ファイルをトリミングして不要な部分を削除したり、特定の部分にフォーカスしたりできます。黒いバーを削除したり、アスペクト比を調整したり、重要なコンテンツにフォーカスしたりするのに便利です。MP4、MOV、AVIなど、様々な動画形式に対応しています。\",\n    \"resultTitle\": \"切り抜かれたビデオ\",\n    \"shortDescription\": \"不要な部分を削除するためにビデオをトリミングする\",\n    \"title\": \"ビデオをトリミング\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使うと、動画ファイルをトリミングして不要な部分を削除できます。X、Y座標と幅、高さの寸法を設定することで、トリミング領域を指定できます。\",\n      \"title\": \"ビデオをトリミング\"\n    },\n    \"videoDimensions\": \"ビデオのサイズ: {{width}} × {{height}} ピクセル\",\n    \"videoInformation\": \"ビデオ情報\",\n    \"width\": \"幅\",\n    \"xCoordinate\": \"X（左）\",\n    \"yCoordinate\": \"Y（上）\"\n  },\n  \"flip\": {\n    \"description\": \"ビデオファイルを水平または垂直に反転します。特殊効果や向きの問題を修正するためにビデオをミラーリングします。\",\n    \"flippingVideo\": \"反転ビデオ\",\n    \"horizontalLabel\": \"水平（ミラー）\",\n    \"inputTitle\": \"入力ビデオ\",\n    \"orientation\": \"オリエンテーション\",\n    \"resultTitle\": \"反転ビデオ\",\n    \"shortDescription\": \"ビデオを水平または垂直に反転する\",\n    \"title\": \"フリップビデオ\",\n    \"verticalLabel\": \"垂直（逆さま）\"\n  },\n  \"gif\": {\n    \"changeSpeed\": {\n      \"description\": \"GIFアニメーションの再生速度を変更します。スムーズなアニメーションを維持しながら、GIFの再生速度を上げたり下げたりできます。\",\n      \"shortDescription\": \"GIFアニメーションの速度を変更する\",\n      \"title\": \"GIFの速度を変更する\"\n    }\n  },\n  \"loop\": {\n    \"description\": \"元のビデオを複数回繰り返してループビデオを作成します。\",\n    \"inputTitle\": \"入力ビデオ\",\n    \"loopingVideo\": \"ループビデオ\",\n    \"loops\": \"ループ\",\n    \"numberOfLoops\": \"ループ数\",\n    \"resultTitle\": \"ループ動画\",\n    \"shortDescription\": \"ループ動画ファイルを作成する\",\n    \"title\": \"ループビデオ\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使えば、元の動画を複数回繰り返し再生することで、ループ動画を作成できます。動画をループさせる回数も指定できます。\",\n      \"title\": \"何ですか {{title}}？\"\n    }\n  },\n  \"mergeVideo\": {\n    \"description\": \"複数のビデオ ファイルを 1 つの連続したビデオに結合します。\",\n    \"longDescription\": \"このツールを使えば、複数の動画ファイルを1本の連続した動画に結合または追加できます。動画ファイルをアップロードし、希望の順序に並べ替えて1つのファイルに結合するだけで、簡単に共有したり編集したりできます。\",\n    \"shortDescription\": \"ビデオを簡単に追加および結合します。\",\n    \"title\": \"ビデオを結合する\"\n  },\n  \"rotate\": {\n    \"180Degrees\": \"180°（上下逆さま）\",\n    \"270Degrees\": \"270°（反時計回り90°）\",\n    \"90Degrees\": \"90°時計回り\",\n    \"description\": \"ビデオファイルを90度、180度、または270度回転できます。正確な回転制御により、ビデオの向きを修正したり、特殊効果を作成したりできます。\",\n    \"inputTitle\": \"入力ビデオ\",\n    \"resultTitle\": \"回転したビデオ\",\n    \"rotatingVideo\": \"回転するビデオ\",\n    \"rotation\": \"回転\",\n    \"shortDescription\": \"指定した角度でビデオを回転する\",\n    \"title\": \"ビデオを回転する\"\n  },\n  \"trim\": {\n    \"description\": \"開始時間と終了時間を指定してビデオファイルをトリミングします。ビデオの先頭または末尾の不要な部分を削除します。\",\n    \"endTime\": \"終了時間\",\n    \"inputTitle\": \"入力ビデオ\",\n    \"resultTitle\": \"トリミングされたビデオ\",\n    \"shortDescription\": \"不要な部分を削除してビデオをトリミングする\",\n    \"startTime\": \"開始時間\",\n    \"timestamps\": \"タイムスタンプ\",\n    \"title\": \"ビデオをトリムする\"\n  },\n  \"videoToGif\": {\n    \"description\": \"ビデオファイルをアニメーションGIF形式に変換します。特定の時間範囲を抽出し、共有可能なアニメーション画像を作成します。\",\n    \"shortDescription\": \"ビデオをアニメーションGIFに変換する\",\n    \"title\": \"ビデオをGIFに変換\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ja/xml.json",
    "content": "{\n  \"xmlBeautifier\": {\n    \"description\": \"適切なインデントとスペースを使用して XML をフォーマットします。\",\n    \"indentation\": \"インデント\",\n    \"inputTitle\": \"入力XML\",\n    \"resultTitle\": \"美化されたXML\",\n    \"shortDescription\": \"XML コードをフォーマットして美しくする\",\n    \"title\": \"XML 美化ツール\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使用すると、適切なインデントと間隔で XML データをフォーマットできるため、読みやすく、操作しやすくなります。\",\n      \"title\": \"XML 美化ツール\"\n    },\n    \"useSpaces\": \"スペースを使用する\",\n    \"useSpacesDescription\": \"スペースで出力をインデントする\",\n    \"useTabs\": \"タブを使用する\",\n    \"useTabsDescription\": \"出力をタブでインデントします。\"\n  },\n  \"xmlValidator\": {\n    \"description\": \"XML 構文と構造を検証します。\",\n    \"placeholder\": \"ここに XML を貼り付けるかインポートします...\",\n    \"shortDescription\": \"XMLコードのエラーを検証する\",\n    \"title\": \"XMLバリデータ\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使うと、XMLの構文と構造を検証できます。XMLが整形式であるかどうかを確認し、問題が見つかった場合は詳細なエラーメッセージを表示します。\",\n      \"title\": \"XMLバリデータ\"\n    }\n  },\n  \"xmlViewer\": {\n    \"description\": \"XML 構造をツリー形式で表示および探索します。\",\n    \"inputTitle\": \"入力XML\",\n    \"resultTitle\": \"XMLツリービュー\",\n    \"title\": \"XMLビューア\",\n    \"toolInfo\": {\n      \"description\": \"このツールを使用すると、XML データを階層ツリー形式で表示できるため、XML ドキュメントの構造を簡単に調査して理解できるようになります。\",\n      \"title\": \"XMLビューア\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/audio.json",
    "content": "{\n  \"changeSpeed\": {\n    \"description\": \"Wijzig de afspeelsnelheid van audiobestanden. Versnel of vertraag audio met behoud van toonhoogte.\",\n    \"inputTitle\": \"Invoer audio\",\n    \"newAudioSpeed\": \"Nieuwe audiosnelheid\",\n    \"outputFormat\": \"Uitvoerformaat\",\n    \"resultTitle\": \"Bewerkte audio\",\n    \"settingSpeed\": \"Snelheid instellen\",\n    \"shortDescription\": \"De snelheid van audiobestanden wijzigen\",\n    \"speedDescription\": \"Standaardvermenigvuldiger: 2 betekent 2x sneller\",\n    \"title\": \"Wijzig audiosnelheid\",\n    \"toolInfo\": {\n      \"title\": \"Wat is {{title}}?\"\n    }\n  },\n  \"extractAudio\": {\n    \"description\": \"Haal een audiotrack uit videobestanden.\",\n    \"extractingAudio\": \"Audio extraheren\",\n    \"inputTitle\": \"Invoervideo\",\n    \"outputFormat\": \"Uitvoerformaat\",\n    \"outputFormatDescription\": \"Selecteer het formaat waarin u de audio wilt extraheren.\",\n    \"resultTitle\": \"Geëxtraheerde audio\",\n    \"shortDescription\": \"Extraheer audio uit videobestanden (MP4, MOV, enz.) naar AAC, MP3 of WAV.\",\n    \"title\": \"Audio uit video extraheren\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kun je de audiotrack uit videobestanden halen. Je kunt kiezen uit verschillende audioformaten, waaronder AAC, MP3 en WAV.\",\n      \"title\": \"Wat is {{title}}?\"\n    }\n  },\n  \"mergeAudio\": {\n    \"description\": \"Combineer meerdere audiobestanden tot één audiobestand door ze in volgorde te plaatsen.\",\n    \"inputTitle\": \"Invoer audiobestanden\",\n    \"longDescription\": \"Met deze tool kun je meerdere audiobestanden samenvoegen tot één bestand door ze te combineren in de volgorde waarin je ze uploadt. Perfect voor het combineren van podcastfragmenten, muzieknummers of andere audiobestanden die moeten worden samengevoegd. Ondersteunt diverse audioformaten, waaronder MP3, AAC en WAV.\",\n    \"mergingAudio\": \"Audio samenvoegen\",\n    \"outputFormat\": \"Uitvoerformaat\",\n    \"resultTitle\": \"Samengevoegde audio\",\n    \"shortDescription\": \"Voeg meerdere audiobestanden samen tot één bestand (MP3, AAC, WAV).\",\n    \"title\": \"Audio samenvoegen\",\n    \"toolInfo\": {\n      \"title\": \"Wat is {{title}}?\"\n    }\n  },\n  \"trim\": {\n    \"description\": \"Knip en trim audiobestanden om specifieke segmenten te extraheren door start- en eindtijden op te geven.\",\n    \"endTime\": \"Eindtijd\",\n    \"endTimeDescription\": \"Eindtijd in het formaat UU:MM:SS (bijv. 00:01:30)\",\n    \"inputTitle\": \"Invoer audio\",\n    \"longDescription\": \"Met deze tool kun je audiobestanden inkorten door de begin- en eindtijd op te geven. Je kunt specifieke segmenten uit langere audiobestanden halen, ongewenste delen verwijderen of kortere clips maken. Ondersteunt diverse audioformaten, waaronder MP3, AAC en WAV. Perfect voor podcastbewerking, muziekproductie of andere audiobewerkingsbehoeften.\",\n    \"outputFormat\": \"Uitvoerformaat\",\n    \"resultTitle\": \"Bijgesneden audio\",\n    \"shortDescription\": \"Trim audiobestanden om specifieke tijdssegmenten te extraheren (MP3, AAC, WAV).\",\n    \"startTime\": \"Starttijd\",\n    \"startTimeDescription\": \"Starttijd in het formaat UU:MM:SS (bijv. 00:00:30)\",\n    \"timeSettings\": \"Tijdinstellingen\",\n    \"title\": \"Audio bijsnijden\",\n    \"toolInfo\": {\n      \"title\": \"Wat is {{title}}?\"\n    },\n    \"trimmingAudio\": \"Audio bijsnijden\"\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/converters.json",
    "content": "{\n  \"audioConverter\": {\n    \"title\": \"Audio Converter\",\n    \"description\": \"Converteer audiobestanden tussen verschillende indelingen.\",\n    \"shortDescription\": \"Converteer audiobestanden naar verschillende indelingen.\",\n    \"longDescription\": \"Met deze tool kunt u audiobestanden van het ene naar het andere formaat converteren, met ondersteuning voor een breed scala aan audioformaten voor naadloze conversie.\",\n    \"outputFormat\": \"Uitvoerindeling\",\n    \"outputFormatDescription\": \"Selecteer het gewenste audio-uitvoerformat\",\n    \"inputTitle\": \"Audio-invoer\",\n    \"outputTitle\": \"Geconverteerde Audio\"\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/csv.json",
    "content": "{\n  \"changeCsvSeparator\": {\n    \"description\": \"Wijzig het scheidingsteken in CSV-bestanden. Converteer tussen verschillende CSV-formaten, zoals komma's, puntkomma's, tabs of aangepaste scheidingstekens.\",\n    \"shortDescription\": \"Wijzig het scheidingsteken van CSV-bestanden\",\n    \"title\": \"CSV-scheidingsteken wijzigen\"\n  },\n  \"csvRowsToColumns\": {\n    \"description\": \"Deze tool converteert rijen van een CSV-bestand (Comma Separated Values) naar kolommen. De horizontale regels worden één voor één uit het invoerbestand (CSV) gehaald, 90 graden gedraaid en als verticale kolommen, gescheiden door komma's, na elkaar weergegeven.', longDescription: 'Deze tool converteert rijen van een CSV-bestand (Comma Separated Values) naar kolommen. Als de invoergegevens bijvoorbeeld 6 rijen bevatten, bevat de uitvoer 6 kolommen en worden de elementen van de rijen van boven naar beneden gerangschikt. In een correct opgemaakte CSV is het aantal waarden in elke rij hetzelfde. Wanneer er echter velden in rijen ontbreken, kan het programma deze herstellen en kunt u kiezen uit de beschikbare opties: ontbrekende gegevens vullen met lege elementen of ontbrekende gegevens vervangen door aangepaste elementen, zoals \\\"ontbrekend\\\", \\\"?\\\" of \\\"x\\\". Tijdens het conversieproces verwijdert de tool ook onnodige informatie uit het CSV-bestand, zoals lege regels (dit zijn regels zonder zichtbare informatie) en opmerkingen. Om de tool te helpen opmerkingen correct te identificeren, kunt u in de opties het symbool opgeven aan het begin van een regel waarmee een opmerking begint. Dit symbool is meestal een hekje \\\"#\\\" of een dubbele slash \\\"//\\\". Csv-abulous!\",\n    \"longDescription\": \"Deze tool converteert rijen van een CSV-bestand (Comma Separated Values) naar kolommen. Als de invoergegevens in CSV-formaat bijvoorbeeld 6 rijen bevatten, heeft de uitvoer 6 kolommen en worden de elementen van de rijen van boven naar beneden gerangschikt. In een correct opgemaakte CSV is het aantal waarden in elke rij hetzelfde. Wanneer er echter velden in rijen ontbreken, kan het programma deze herstellen en kunt u kiezen uit de beschikbare opties: ontbrekende gegevens vullen met lege elementen of ontbrekende gegevens vervangen door aangepaste elementen, zoals\",\n    \"shortDescription\": \"Converteer CSV-rijen naar kolommen.\",\n    \"title\": \"CSV-rijen naar kolommen converteren\"\n  },\n  \"csvToJson\": {\n    \"columnSeparator\": \"Kolomscheidingsteken (bijv. , ; \\\\t)\",\n    \"commentSymbol\": \"Commentaarsymbool (bijv. #)\",\n    \"conversionOptions\": \"Conversie-opties\",\n    \"description\": \"Converteer CSV-bestanden naar JSON-formaat met aanpasbare opties voor scheidingstekens, aanhalingstekens en uitvoeropmaak. Ondersteuning voor kopteksten, opmerkingen en dynamische typeconversie.\",\n    \"dynamicTypes\": \"Dynamische typen\",\n    \"dynamicTypesDescription\": \"Automatisch getallen en Booleaanse waarden converteren\",\n    \"error\": \"Fout\",\n    \"errorParsing\": \"Fout bij het parseren van CSV: {{error}}\",\n    \"fieldQuote\": \"Veldcitaat (bijv. \\\")\",\n    \"inputCsvFormat\": \"CSV-invoerformaat\",\n    \"inputTitle\": \"CSV-invoer\",\n    \"invalidCsvFormat\": \"Ongeldige CSV-indeling\",\n    \"resultTitle\": \"Uitvoer JSON\",\n    \"shortDescription\": \"Converteer CSV-gegevens naar JSON-formaat.\",\n    \"skipEmptyLines\": \"Lege regels overslaan\",\n    \"skipEmptyLinesDescription\": \"Negeer lege regels in de invoer-CSV\",\n    \"title\": \"CSV naar JSON converteren\",\n    \"toolInfo\": {\n      \"description\": \"Deze tool transformeert CSV-bestanden (Comma Separated Values) naar JSON-datastructuren (JavaScript Object Notation). Het ondersteunt diverse CSV-formaten met aanpasbare scheidingstekens, aanhalingstekens en commentaarsymbolen. De converter kan de eerste rij als kopteksten behandelen, lege regels overslaan en automatisch gegevenstypen zoals getallen en Booleaanse waarden detecteren. De resulterende JSON kan worden gebruikt voor datamigratie, back-ups of als invoer voor andere applicaties.\",\n      \"title\": \"Wat is een CSV naar JSON-converter?\"\n    },\n    \"useHeaders\": \"Gebruik headers\",\n    \"useHeadersDescription\": \"Behandel de eerste rij als kolomkoppen\"\n  },\n  \"csvToTsv\": {\n    \"description\": \"Upload uw CSV-bestand via onderstaand formulier en het wordt automatisch omgezet naar een TSV-bestand. In de toolopties kunt u het CSV-invoerformaat aanpassen: geef het veldscheidingsteken, het aanhalingsteken en het commentaarsymbool op, sla lege CSV-regels over en kies of u CSV-kolomkoppen wilt behouden.\",\n    \"longDescription\": \"Deze tool zet Comma Separated Values (CSV)-gegevens om in Tab Separated Values (TSV). Zowel CSV als TSV zijn populaire bestandsformaten voor het opslaan van tabelgegevens, maar ze gebruiken verschillende scheidingstekens om waarden te scheiden. CSV gebruikt komma's (\",\n    \"shortDescription\": \"Converteer CSV-gegevens naar TSV-formaat.\",\n    \"title\": \"CSV naar TSV converteren\"\n  },\n  \"csvToXml\": {\n    \"description\": \"Converteer CSV-bestanden naar XML-formaat met aanpasbare opties.\",\n    \"shortDescription\": \"Converteer CSV-gegevens naar XML-formaat.\",\n    \"title\": \"CSV naar XML converteren\"\n  },\n  \"csvToYaml\": {\n    \"description\": \"Upload uw CSV-bestand via onderstaand formulier en het wordt automatisch omgezet naar een YAML-bestand. In de toolopties kunt u het scheidingsteken voor velden, het aanhalingsteken voor velden en het commentaarteken opgeven om de tool aan te passen aan aangepaste CSV-indelingen. Daarnaast kunt u de uitvoer-YAML-indeling selecteren: een indeling die CSV-headers behoudt of een indeling die CSV-headers uitsluit.\",\n    \"longDescription\": \"Deze tool transformeert CSV-gegevens (Comma Separated Values) naar YAML-gegevens (Yet Another Markup Language). CSV is een eenvoudig tabelformaat dat wordt gebruikt om matrixachtige gegevenstypen weer te geven die bestaan uit rijen en kolommen. YAML daarentegen is een geavanceerder formaat (eigenlijk een superset van JSON), dat beter leesbare gegevens voor serialisatie genereert en lijsten, woordenboeken en geneste objecten ondersteunt. Dit programma ondersteunt verschillende CSV-invoerformaten: de invoergegevens kunnen door komma's (standaard), door puntkomma's, door slierten of een ander scheidingsteken worden gescheiden. U kunt het exacte scheidingsteken dat uw gegevens gebruiken in de opties opgeven. Op dezelfde manier kunt u in de opties het aanhalingsteken opgeven dat wordt gebruikt om CSV-velden in te sluiten (standaard een dubbel aanhalingsteken). U kunt ook regels overslaan die beginnen met opmerkingen door de opmerkingsymbolen in de opties op te geven. Zo houdt u uw gegevens overzichtelijk door onnodige regels over te slaan. Er zijn twee manieren om CSV naar YAML te converteren. De eerste methode converteert elke CSV-rij naar een YAML-lijst. De tweede methode extraheert headers uit de eerste CSV-rij en creëert YAML-objecten met sleutels op basis van deze headers. U kunt de YAML-uitvoer ook aanpassen door het aantal spaties voor inspringing van YAML-structuren op te geven. Als u de omgekeerde conversie wilt uitvoeren, dat wil zeggen YAML naar CSV wilt converteren, kunt u onze tool YAML naar CSV converteren gebruiken. Csv-abulous!\",\n    \"shortDescription\": \"Converteer snel een CSV-bestand naar een YAML-bestand.\",\n    \"title\": \"CSV naar YAML converteren\"\n  },\n  \"findIncompleteCsvRecords\": {\n    \"checkingOptions\": \"Opties controleren\",\n    \"commentCharacterDescription\": \"Voer het teken in dat het begin van een commentaarregel aangeeft. Regels die met dit symbool beginnen, worden overgeslagen.\",\n    \"csvInputOptions\": \"CSV-invoeropties\",\n    \"csvSeparatorDescription\": \"Voer het teken in dat gebruikt wordt om kolommen in het CSV-invoerbestand te scheiden.\",\n    \"deleteLinesWithNoData\": \"Regels zonder gegevens verwijderen\",\n    \"deleteLinesWithNoDataDescription\": \"Verwijder lege regels uit het CSV-invoerbestand.\",\n    \"description\": \"Upload uw CSV-bestand via onderstaand formulier en deze tool controleert automatisch of er geen waarden in de rijen of kolommen ontbreken. In de toolopties kunt u de opmaak van het invoerbestand aanpassen (het scheidingsteken, de aanhalingstekens en het commentaarteken opgeven). Daarnaast kunt u de controle op lege waarden inschakelen, lege regels overslaan en een limiet instellen voor het aantal foutmeldingen in de uitvoer.\",\n    \"findEmptyValues\": \"Lege waarden zoeken\",\n    \"findEmptyValuesDescription\": \"Geef een bericht weer over lege CSV-velden (dit zijn geen ontbrekende velden, maar velden die niets bevatten).\",\n    \"inputTitle\": \"CSV-invoer\",\n    \"limitNumberOfMessages\": \"Beperk het aantal berichten\",\n    \"messageLimitDescription\": \"Stel de limiet in voor het aantal berichten in de uitvoer.\",\n    \"quoteCharacterDescription\": \"Voer het aanhalingsteken in dat u wilt gebruiken om de CSV-invoervelden te citeren.\",\n    \"resultTitle\": \"CSV-status\",\n    \"shortDescription\": \"Vind snel rijen en kolommen in CSV waar waarden ontbreken.\",\n    \"title\": \"Onvolledige CSV-records vinden\",\n    \"toolInfo\": {\n      \"title\": \"Wat is een {{title}}?\"\n    }\n  },\n  \"insertCsvColumns\": {\n    \"appendColumns\": \"Kolommen toevoegen\",\n    \"commentCharacterDescription\": \"Voer het teken in dat het begin van een commentaarregel aangeeft. Regels die met dit symbool beginnen, worden overgeslagen.\",\n    \"csvOptions\": \"CSV-opties\",\n    \"csvSeparator\": \"CSV-scheidingsteken\",\n    \"csvToInsert\": \"CSV om in te voegen\",\n    \"csvToInsertDescription\": \"Voer een of meer kolommen in die u in het CSV-bestand wilt invoegen. Het teken dat wordt gebruikt om kolommen te scheiden, moet hetzelfde zijn als het teken in het CSV-invoerbestand. Let op: lege regels worden genegeerd.\",\n    \"customFillDescription\": \"Als het invoer-CSV-bestand niet compleet is (ontbrekende waarden), kunt u dan lege velden of aangepaste symbolen aan de records toevoegen om een goed opgemaakte CSV te maken?\",\n    \"customFillValueDescription\": \"Gebruik deze aangepaste waarde om ontbrekende velden in te vullen. (Werkt alleen met de modus 'Aangepaste waarden' hierboven.)\",\n    \"customPosition\": \"Aangepaste positie\",\n    \"customPositionOptionsDescription\": \"Selecteer de methode om de kolommen in het CSV-bestand in te voegen.\",\n    \"description\": \"Voeg nieuwe kolommen toe aan CSV-gegevens op de opgegeven posities.\",\n    \"fillWithCustomValues\": \"Vul met douanewaarden\",\n    \"fillWithEmptyValues\": \"Vullen met lege waarden\",\n    \"headerName\": \"Koptekstnaam\",\n    \"headerNameDescription\": \"Koptekst van de kolom waarna u kolommen wilt invoegen.\",\n    \"inputTitle\": \"CSV-invoer\",\n    \"insertingPositionDescription\": \"Geef aan waar de kolommen in het CSV-bestand moeten worden ingevoegd.\",\n    \"position\": \"Positie\",\n    \"positionOptions\": \"Positieopties\",\n    \"prependColumns\": \"Kolommen vooraf toevoegen\",\n    \"quoteCharDescription\": \"Voer het aanhalingsteken in dat u wilt gebruiken om de CSV-invoervelden te citeren.\",\n    \"resultTitle\": \"CSV-uitvoer\",\n    \"rowNumberDescription\": \"Nummer van de kolom waarna u kolommen wilt invoegen.\",\n    \"separatorDescription\": \"Voer het teken in dat gebruikt wordt om kolommen in het CSV-invoerbestand te scheiden.\",\n    \"shortDescription\": \"Voeg snel een of meer nieuwe kolommen in, waar dan ook in een CSV-bestand.\",\n    \"title\": \"CSV-kolommen invoegen\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u nieuwe kolommen op specifieke posities in CSV-gegevens invoegen. U kunt kolommen op aangepaste posities voorvoegen, toevoegen of invoegen op basis van koptekstnamen of kolomnummers.\",\n      \"title\": \"CSV-kolommen invoegen\"\n    }\n  },\n  \"swapCsvColumns\": {\n    \"description\": \"Upload uw CSV-bestand via het onderstaande formulier, geef aan welke kolommen u wilt omwisselen en de tool wijzigt automatisch de posities van de opgegeven kolommen in het uitvoerbestand. In de toolopties kunt u de kolomposities of -namen opgeven die u wilt omwisselen, onvolledige gegevens corrigeren en optioneel lege records en uitgeschakelde records verwijderen.\",\n    \"longDescription\": \"Deze tool reorganiseert CSV-gegevens door de posities van de kolommen om te wisselen. Het omwisselen van kolommen kan de leesbaarheid van een CSV-bestand verbeteren door veelgebruikte gegevens samen of vooraan te plaatsen, voor eenvoudigere gegevensvergelijking en -bewerking. U kunt bijvoorbeeld de eerste kolom omwisselen met de laatste of de tweede kolom met de derde. Om kolommen op basis van hun positie om te wisselen, selecteert u\",\n    \"shortDescription\": \"CSV-kolommen opnieuw ordenen.\",\n    \"title\": \"CSV-kolommen verwisselen\"\n  },\n  \"transposeCsv\": {\n    \"description\": \"Upload uw CSV-bestand via onderstaand formulier en deze tool transponeert uw CSV-bestand automatisch. In de toolopties kunt u het teken opgeven waarmee de commentaarregels in het CSV-bestand beginnen om ze te verwijderen. Als het CSV-bestand onvolledig is (ontbrekende waarden), kunt u de ontbrekende waarden vervangen door het lege teken of een aangepast teken.\",\n    \"longDescription\": \"Deze tool transponeert Comma Separated Values (CSV). Het behandelt de CSV als een matrix met data en draait alle elementen om over de hoofddiagonaal. De uitvoer bevat dezelfde CSV-data als de invoer, maar nu zijn alle rijen kolommen en alle kolommen rijen geworden. Na transpositie heeft het CSV-bestand tegengestelde afmetingen. Als het invoerbestand bijvoorbeeld 4 kolommen en 3 rijen heeft, heeft het uitvoerbestand 3 kolommen en 4 rijen. Tijdens de conversie zuivert het programma de data ook van onnodige regels en corrigeert het onvolledige data. De tool verwijdert automatisch alle lege records en opmerkingen die beginnen met een specifiek teken, dat u in de optie kunt instellen. Bovendien, in gevallen waarin de CSV-data beschadigd of verloren zijn, vult het hulpprogramma het bestand aan met lege velden of aangepaste velden die u in de opties kunt opgeven. Csv-abulous!\",\n    \"shortDescription\": \"Snel een CSV-bestand transponeren.\",\n    \"title\": \"CSV transponeren\"\n  },\n  \"tsvToJson\": {\n    \"description\": \"Converteer TSV-gegevens (Tab-Separated Values) naar JSON-formaat. Transformeer tabelgegevens naar gestructureerde JSON-objecten.\",\n    \"shortDescription\": \"Converteer TSV naar JSON-formaat\",\n    \"title\": \"TSV naar JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/image.json",
    "content": "{\n  \"changeColors\": {\n    \"description\": \"Wereld\",\n    \"shortDescription\": \"Snel kleuren in een afbeelding verwisselen\",\n    \"title\": \"Kleuren in afbeelding wijzigen\"\n  },\n  \"changeOpacity\": {\n    \"description\": \"Pas de transparantie van je afbeeldingen eenvoudig aan. Upload je afbeelding, gebruik de schuifbalk om de gewenste dekking in te stellen tussen 0 (volledig transparant) en 1 (volledig ondoorzichtig) en download de aangepaste afbeelding.\",\n    \"shortDescription\": \"Pas de transparantie van afbeeldingen aan\",\n    \"title\": \"Afbeeldingsdekking wijzigen\"\n  },\n  \"compress\": {\n    \"compressedSize\": \"Gecomprimeerde grootte\",\n    \"compressionOptions\": \"Compressie-opties\",\n    \"description\": \"Verklein de bestandsgrootte van een afbeelding, maar behoud de kwaliteit.\",\n    \"failedToCompress\": \"Het comprimeren van de afbeelding is mislukt. Probeer het opnieuw.\",\n    \"fileSizes\": \"Bestandsgroottes\",\n    \"inputTitle\": \"Invoerafbeelding\",\n    \"maxFileSizeDescription\": \"Maximale bestandsgrootte in megabytes\",\n    \"originalSize\": \"Originele grootte\",\n    \"qualityDescription\": \"Beeldkwaliteitspercentage (lager betekent kleinere bestandsgrootte)\",\n    \"resultTitle\": \"Gecomprimeerde afbeelding\",\n    \"shortDescription\": \"Comprimeer afbeeldingen om de bestandsgrootte te verkleinen, maar zorg er wel voor dat de kwaliteit redelijk blijft.\",\n    \"title\": \"Afbeelding comprimeren\"\n  },\n  \"compressPng\": {\n    \"description\": \"Dit is een programma dat PNG-afbeeldingen comprimeert. Zodra u uw PNG-afbeelding in het invoerveld plakt, comprimeert het programma deze en toont het resultaat in het uitvoerveld. In de opties kunt u het compressieniveau aanpassen en de oude en nieuwe bestandsgroottes van de afbeeldingen vinden.\",\n    \"shortDescription\": \"Snel een PNG comprimeren\",\n    \"title\": \"PNG comprimeren\"\n  },\n  \"convertJgpToPng\": {\n    \"description\": \"Converteer je JPG-afbeeldingen snel naar PNG. Importeer je PNG-afbeelding in de editor aan de linkerkant.\",\n    \"shortDescription\": \"Converteer uw JPG-afbeeldingen snel naar PNG\",\n    \"title\": \"JPG naar PNG converteren\"\n  },\n  \"convertToJpg\": {\n    \"description\": \"Converteer verschillende afbeeldingsformaten (PNG, GIF, TIF, PSD, SVG, WEBP, HEIC, RAW) naar JPG met aanpasbare instellingen voor de kwaliteit en achtergrondkleur.\",\n    \"shortDescription\": \"Converteer afbeeldingen naar JPG met kwaliteitscontrole\",\n    \"title\": \"Afbeeldingen naar JPG converteren\"\n  },\n  \"createTransparent\": {\n    \"description\": \"Wereld\",\n    \"shortDescription\": \"Snel een afbeelding transparant maken\",\n    \"title\": \"Transparante PNG maken\"\n  },\n  \"crop\": {\n    \"description\": \"Snijd afbeeldingen bij om ongewenste delen te verwijderen.\",\n    \"inputTitle\": \"Invoerafbeelding\",\n    \"resultTitle\": \"Bijgesneden afbeelding\",\n    \"shortDescription\": \"Snel afbeeldingen bijsnijden.\",\n    \"title\": \"Afbeelding bijsnijden\"\n  },\n  \"editor\": {\n    \"description\": \"Geavanceerde beeldbewerker met tools voor bijsnijden, roteren, annoteren, kleuren aanpassen en watermerken toevoegen. Bewerk je afbeeldingen met professionele tools rechtstreeks in je browser.\",\n    \"shortDescription\": \"Bewerk afbeeldingen met geavanceerde tools en functies\",\n    \"title\": \"Afbeeldingseditor\"\n  },\n  \"imageToText\": {\n    \"description\": \"Extraheer tekst uit afbeeldingen (JPG, PNG) met behulp van optische tekenherkenning (OCR).\",\n    \"shortDescription\": \"Tekst uit afbeeldingen extraheren met OCR.\",\n    \"title\": \"Afbeelding naar tekst (OCR)\"\n  },\n  \"qrCode\": {\n    \"description\": \"Genereer QR-codes voor verschillende gegevenstypen: URL, tekst, e-mail, telefoon, sms, wifi, vCard en meer.\",\n    \"shortDescription\": \"Maak aangepaste QR-codes voor verschillende gegevensformaten.\",\n    \"title\": \"QR-codegenerator\"\n  },\n  \"removeBackground\": {\n    \"description\": \"Wereld\",\n    \"shortDescription\": \"Achtergronden automatisch uit afbeeldingen verwijderen\",\n    \"title\": \"Achtergrond uit afbeelding verwijderen\"\n  },\n  \"resize\": {\n    \"description\": \"Afbeeldingen aanpassen naar verschillende afmetingen.\",\n    \"dimensionType\": \"Dimensietype\",\n    \"heightDescription\": \"Hoogte (in pixels)\",\n    \"inputTitle\": \"Invoerafbeelding\",\n    \"maintainAspectRatio\": \"Beeldverhouding behouden\",\n    \"maintainAspectRatioDescription\": \"Behoud de originele beeldverhouding van de afbeelding.\",\n    \"percentage\": \"Percentage\",\n    \"percentageDescription\": \"Percentage van de oorspronkelijke grootte (bijv. 50 voor halve grootte, 200 voor dubbele grootte)\",\n    \"resizeByPercentage\": \"Formaat wijzigen met percentage\",\n    \"resizeByPercentageDescription\": \"U kunt de grootte wijzigen door een percentage van de oorspronkelijke grootte op te geven.\",\n    \"resizeByPixels\": \"Formaat wijzigen met pixels\",\n    \"resizeByPixelsDescription\": \"U kunt het formaat wijzigen door de afmetingen in pixels op te geven.\",\n    \"resizeMethod\": \"Methode voor het wijzigen van de grootte\",\n    \"resultTitle\": \"Afbeelding met gewijzigd formaat\",\n    \"setHeight\": \"Hoogte instellen\",\n    \"setHeightDescription\": \"Geef de hoogte op in pixels en bereken de breedte op basis van de beeldverhouding.\",\n    \"setWidth\": \"Breedte instellen\",\n    \"setWidthDescription\": \"Geef de breedte op in pixels en bereken de hoogte op basis van de beeldverhouding.\",\n    \"shortDescription\": \"U kunt de grootte van afbeeldingen eenvoudig aanpassen.\",\n    \"title\": \"Afbeelding formaat wijzigen\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kun je de grootte van JPG-, PNG-, SVG- of GIF-afbeeldingen aanpassen. Je kunt de grootte aanpassen door de afmetingen in pixels of percentages op te geven, met opties om de oorspronkelijke beeldverhouding te behouden.\",\n      \"title\": \"Afbeelding formaat wijzigen\"\n    },\n    \"widthDescription\": \"Breedte (in pixels)\"\n  },\n  \"rotate\": {\n    \"description\": \"Draai een afbeelding met een bepaalde hoek.\",\n    \"shortDescription\": \"Draai een afbeelding eenvoudig.\",\n    \"title\": \"Afbeelding roteren\"\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/json.json",
    "content": "{\n  \"comparison\": {\n    \"description\": \"Vergelijk twee JSON-objecten om verschillen in structuur en waarden te identificeren.\",\n    \"shortDescription\": \"Vind verschillen tussen twee JSON-objecten\",\n    \"title\": \"JSON vergelijken\"\n  },\n  \"escapeJson\": {\n    \"description\": \"Escape speciale tekens in JSON-strings. Converteer JSON-gegevens naar een correct geëscapete indeling voor veilige overdracht of opslag.\",\n    \"shortDescription\": \"Speciale tekens in JSON ontsnappen\",\n    \"title\": \"Ontsnappen aan JSON\"\n  },\n  \"jsonToXml\": {\n    \"description\": \"Converteer JSON-gegevens naar XML-formaat. Transformeer gestructureerde JSON-objecten naar correct vormgegeven XML-documenten.\",\n    \"shortDescription\": \"Converteer JSON naar XML-formaat\",\n    \"title\": \"JSON naar XML\"\n  },\n  \"minify\": {\n    \"description\": \"Verwijder alle onnodige witruimte uit JSON.\",\n    \"inputTitle\": \"Invoer JSON\",\n    \"resultTitle\": \"Geminimaliseerde JSON\",\n    \"shortDescription\": \"Verklein JSON door witruimte te verwijderen\",\n    \"title\": \"JSON verkleinen\",\n    \"toolInfo\": {\n      \"description\": \"JSON-minificatie is het proces waarbij alle onnodige witruimte uit JSON-gegevens wordt verwijderd, terwijl de geldigheid ervan behouden blijft. Dit omvat het verwijderen van spaties, nieuwe regels en inspringingen die niet nodig zijn voor een correcte parsering van de JSON. Minificatie verkleint de grootte van JSON-gegevens, waardoor deze efficiënter worden opgeslagen en verzonden, met behoud van exact dezelfde datastructuur en waarden.\",\n      \"title\": \"Wat is JSON-minificatie?\"\n    }\n  },\n  \"prettify\": {\n    \"description\": \"Formatteer JSON met de juiste inspringing en spatie.\",\n    \"indentation\": \"Inspringing\",\n    \"inputTitle\": \"Invoer JSON\",\n    \"resultTitle\": \"Verfraaide JSON\",\n    \"shortDescription\": \"JSON-code opmaken en verfraaien\",\n    \"title\": \"JSON verfraaien\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u JSON-gegevens opmaken met de juiste inspringing en spaties, waardoor ze beter leesbaar en eenvoudiger te gebruiken zijn.\",\n      \"title\": \"JSON verfraaien\"\n    },\n    \"useSpaces\": \"Gebruik spaties\",\n    \"useSpacesDescription\": \"Uitvoer inspringen met spaties\",\n    \"useTabs\": \"Tabbladen gebruiken\",\n    \"useTabsDescription\": \"Uitvoer inspringen met tabs.\"\n  },\n  \"stringify\": {\n    \"description\": \"Converteer JavaScript-objecten naar JSON-stringformaat. Serialiseer datastructuren naar JSON-strings voor opslag of verzending.\",\n    \"shortDescription\": \"Objecten converteren naar JSON-string\",\n    \"title\": \"Stringify JSON\"\n  },\n  \"validateJson\": {\n    \"description\": \"Controleer of JSON geldig en correct is geformuleerd.\",\n    \"inputTitle\": \"Invoer JSON\",\n    \"invalidJson\": \"❌ {{error}}\",\n    \"resultTitle\": \"Validatieresultaat\",\n    \"shortDescription\": \"Valideer JSON-code op fouten\",\n    \"title\": \"Valideer JSON\",\n    \"toolInfo\": {\n      \"description\": \"JSON (JavaScript Object Notation) is een lichtgewicht formaat voor gegevensuitwisseling. JSON-validatie zorgt ervoor dat de structuur van de gegevens voldoet aan de JSON-standaard. Een geldig JSON-object moet het volgende bevatten: - Eigenschapsnamen tussen dubbele aanhalingstekens. - Correct gebalanceerde accolades {}. - Geen afsluitende komma's na het laatste sleutel-waardepaar. - Correcte nesting van objecten en arrays. Deze tool controleert de invoer-JSON en geeft feedback om veelvoorkomende fouten te identificeren en te verhelpen.\",\n      \"title\": \"Wat is JSON-validatie?\"\n    },\n    \"validJson\": \"✅ Geldige JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/list.json",
    "content": "{\n  \"duplicate\": {\n    \"concatenate\": \"Concateneren\",\n    \"concatenateDescription\": \"Kopieën samenvoegen (indien niet aangevinkt, worden de items verweven)\",\n    \"copyDescription\": \"Aantal kopieën (kan fractioneel zijn)\",\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het dupliceren van lijstitems. Voer uw lijst in en specificeer duplicatiecriteria om kopieën van items te maken. Perfect voor data-uitbreiding, testen of het creëren van herhaalde patronen.\",\n    \"duplicationOptions\": \"Duplicatieopties\",\n    \"error\": \"Fout\",\n    \"example1Description\": \"Dit voorbeeld laat zien hoe u een lijst met woorden kunt dupliceren.\",\n    \"example1Title\": \"Eenvoudige duplicatie\",\n    \"example2Description\": \"Dit voorbeeld laat zien hoe u een lijst in omgekeerde volgorde kunt dupliceren.\",\n    \"example2Title\": \"Omgekeerde duplicatie\",\n    \"example3Description\": \"Dit voorbeeld laat zien hoe u items kunt verweven in plaats van aaneenschakelen.\",\n    \"example3Title\": \"Verweven items\",\n    \"example4Description\": \"Dit voorbeeld laat zien hoe u een lijst kunt dupliceren met een fractioneel aantal kopieën.\",\n    \"example4Title\": \"Fractionele duplicatie\",\n    \"examples\": {\n      \"fractional\": {\n        \"description\": \"Dit voorbeeld laat zien hoe u een lijst kunt dupliceren met een fractioneel aantal kopieën.\",\n        \"title\": \"Fractionele duplicatie\"\n      },\n      \"interweave\": {\n        \"description\": \"Dit voorbeeld laat zien hoe u items kunt verweven in plaats van aaneenschakelen.\",\n        \"title\": \"Verweven items\"\n      },\n      \"reverse\": {\n        \"description\": \"Dit voorbeeld laat zien hoe u een lijst in omgekeerde volgorde kunt dupliceren.\",\n        \"title\": \"Omgekeerde duplicatie\"\n      },\n      \"simple\": {\n        \"description\": \"Dit voorbeeld laat zien hoe u een lijst met woorden kunt dupliceren.\",\n        \"title\": \"Eenvoudige duplicatie\"\n      }\n    },\n    \"inputTitle\": \"Invoerlijst\",\n    \"joinSeparatorDescription\": \"Scheidingsteken om de gedupliceerde lijst samen te voegen\",\n    \"resultTitle\": \"Gedupliceerde lijst\",\n    \"reverse\": \"Achteruit\",\n    \"reverseDescription\": \"Draai de gedupliceerde items om\",\n    \"shortDescription\": \"Dubbele lijstitems met opgegeven criteria\",\n    \"splitByRegex\": \"Gesplitst door reguliere expressie\",\n    \"splitBySymbol\": \"Gesplitst op symbool\",\n    \"splitOptions\": \"Gesplitste opties\",\n    \"splitSeparatorDescription\": \"Scheidingsteken om de lijst te splitsen\",\n    \"title\": \"Duplicaat\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u items in een lijst dupliceren. U kunt het aantal kopieën (inclusief fractionele waarden) opgeven, bepalen of items worden samengevoegd of verweven, en zelfs de gedupliceerde items omkeren. Dit is handig voor het creëren van herhaalde patronen, het genereren van testgegevens of het uitbreiden van lijsten met voorspelbare inhoud.\",\n      \"title\": \"Lijstduplicatie\"\n    },\n    \"unknownError\": \"Er is een onbekende fout opgetreden\",\n    \"validation\": {\n      \"copyMustBeNumber\": \"Aantal exemplaren moet een getal zijn\",\n      \"copyMustBePositive\": \"Het aantal kopieën moet positief zijn\",\n      \"copyRequired\": \"Aantal exemplaren is vereist\",\n      \"joinSeparatorRequired\": \"Het join-scheidingsteken is vereist\",\n      \"separatorRequired\": \"De scheidingsteken is vereist\"\n    }\n  },\n  \"findMostPopular\": {\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde tool om de populairste items in een lijst te vinden. Voer je lijst in en krijg direct de items die het vaakst voorkomen. Perfect voor data-analyse, trendidentificatie of het vinden van gemeenschappelijke elementen.\",\n    \"displayFormatDescription\": \"Hoe geef ik de populairste lijstitems weer?\",\n    \"displayOptions\": {\n      \"count\": \"Toon aantal items\",\n      \"percentage\": \"Itempercentage weergeven\",\n      \"total\": \"Toon item totaal\"\n    },\n    \"extractListItems\": \"Hoe kan ik lijst-items extraheren?\",\n    \"ignoreItemCase\": \"Negeer itemcase\",\n    \"ignoreItemCaseDescription\": \"Vergelijk alle lijstitems in kleine letters.\",\n    \"inputTitle\": \"Invoerlijst\",\n    \"itemComparison\": \"Artikelvergelijking\",\n    \"outputFormat\": \"Top item-uitvoerformaat\",\n    \"removeEmptyItems\": \"Lege items verwijderen\",\n    \"removeEmptyItemsDescription\": \"Negeer lege items in de vergelijking.\",\n    \"resultTitle\": \"Meest populaire artikelen\",\n    \"shortDescription\": \"Vind de meest voorkomende items\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Alfabetisch sorteren\",\n      \"count\": \"Sorteren op aantal\"\n    },\n    \"sortingMethodDescription\": \"Selecteer een sorteermethode.\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Beperk de items in de invoerlijst met een reguliere expressie.\",\n        \"title\": \"Gebruik een Regex voor het splitsen\"\n      },\n      \"symbol\": {\n        \"description\": \"Beperk de items in de invoerlijst met een teken.\",\n        \"title\": \"Gebruik een symbool voor splitsen\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Stel een scheidingsteken of reguliere expressie in.\",\n    \"title\": \"Vind de meest populaire\",\n    \"trimItems\": \"Items in de bovenste lijst bijsnijden\",\n    \"trimItemsDescription\": \"Verwijder voorloop- en volgspaties voordat u items vergelijkt\"\n  },\n  \"findUnique\": {\n    \"caseSensitiveItems\": \"Hoofdlettergevoelige items\",\n    \"caseSensitiveItemsDescription\": \"Geef items met verschillende hoofdletters/kleine letters weer als unieke elementen in de lijst.\",\n    \"delimiterDescription\": \"Stel een scheidingsteken of reguliere expressie in.\",\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde tool voor het vinden van unieke items in een lijst. Voer uw lijst in en ontvang direct alle unieke waarden, inclusief verwijderde duplicaten. Perfect voor dataopschoning, deduplicatie of het vinden van afzonderlijke elementen.\",\n    \"findAbsolutelyUniqueItems\": \"Vind absoluut unieke items\",\n    \"findAbsolutelyUniqueItemsDescription\": \"Geef alleen die items van de lijst weer die in één exemplaar aanwezig zijn.\",\n    \"inputListDelimiter\": \"Invoerlijst scheidingsteken\",\n    \"inputTitle\": \"Invoerlijst\",\n    \"outputListDelimiter\": \"Uitvoerlijstscheidingsteken\",\n    \"resultTitle\": \"Unieke items\",\n    \"shortDescription\": \"Vind unieke items in een lijst\",\n    \"skipEmptyItems\": \"Lege items overslaan\",\n    \"skipEmptyItemsDescription\": \"Neem de lege lijst-items niet op in de uitvoer.\",\n    \"title\": \"Vind uniek\",\n    \"trimItems\": \"Items in de trimlijst\",\n    \"trimItemsDescription\": \"Verwijder spaties voor en na de items voordat u ze vergelijkt.\",\n    \"uniqueItemOptions\": \"Unieke itemopties\"\n  },\n  \"group\": {\n    \"deleteEmptyItems\": \"Lege items verwijderen\",\n    \"deleteEmptyItemsDescription\": \"Negeer lege items en neem ze niet op in de groepen.\",\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het groeperen van lijstitems. Voer uw lijst in en specificeer groeperingscriteria om items in logische groepen te ordenen. Perfect voor het categoriseren van gegevens, het organiseren van informatie of het maken van gestructureerde lijsten. Ondersteunt aangepaste scheidingstekens en diverse groeperingsopties.\",\n    \"emptyItemsAndPadding\": \"Lege items en opvulling\",\n    \"groupNumberDescription\": \"Aantal items in een groep\",\n    \"groupSeparatorDescription\": \"Groepsscheidingsteken\",\n    \"groupSizeAndSeparators\": \"Groepsgrootte en scheidingstekens\",\n    \"inputItemSeparator\": \"Scheidingsteken voor invoeritems\",\n    \"inputTitle\": \"Invoerlijst\",\n    \"itemSeparatorDescription\": \"Itemscheidingsteken\",\n    \"leftWrapDescription\": \"Linksomloopsymbool van de groep.\",\n    \"padNonFullGroups\": \"Pad Niet-volledige groepen\",\n    \"padNonFullGroupsDescription\": \"Vul niet-volledige groepen met een aangepast item (voer hieronder in).\",\n    \"paddingCharDescription\": \"Gebruik dit karakter of item om groepen aan te vullen die niet vol zijn.\",\n    \"resultTitle\": \"Gegroepeerde items\",\n    \"rightWrapDescription\": \"Symbool voor de rechteromslag van de groep.\",\n    \"shortDescription\": \"Groepeer lijstitems op basis van gemeenschappelijke eigenschappen\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Beperk de items in de invoerlijst met een reguliere expressie.\",\n        \"title\": \"Gebruik een Regex voor het splitsen\"\n      },\n      \"symbol\": {\n        \"description\": \"Beperk de items in de invoerlijst met een teken.\",\n        \"title\": \"Gebruik een symbool voor splitsen\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Stel een scheidingsteken of reguliere expressie in.\",\n    \"title\": \"Groep\"\n  },\n  \"reverse\": {\n    \"description\": \"Dit is een supereenvoudige browserapplicatie die alle items in de lijst in spiegelbeeld afdrukt. De invoeritems kunnen worden gescheiden door een willekeurig symbool en u kunt ook de scheidingstekens van de omgekeerde items in de lijst wijzigen.\",\n    \"inputTitle\": \"Invoerlijst\",\n    \"itemSeparator\": \"Itemscheidingsteken\",\n    \"itemSeparatorDescription\": \"Stel een scheidingsteken of reguliere expressie in.\",\n    \"outputListOptions\": \"Opties voor uitvoerlijst\",\n    \"outputSeparatorDescription\": \"Scheidingsteken voor uitvoerlijst-items.\",\n    \"resultTitle\": \"Omgekeerde lijst\",\n    \"shortDescription\": \"Een lijst snel omkeren\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Beperk de items in de invoerlijst met een reguliere expressie.\",\n        \"title\": \"Gebruik een Regex voor het splitsen\"\n      },\n      \"symbol\": {\n        \"description\": \"Beperk de items in de invoerlijst met een teken.\",\n        \"title\": \"Gebruik een symbool voor splitsen\"\n      }\n    },\n    \"splitterMode\": \"Splittermodus\",\n    \"title\": \"Achteruit\",\n    \"toolInfo\": {\n      \"description\": \"Met dit hulpprogramma kunt u de volgorde van items in een lijst omkeren. Het hulpprogramma splitst de invoerlijst eerst op in afzonderlijke items en doorloopt deze vervolgens van het laatste naar het eerste item, waarbij elk item tijdens de iteratie in de uitvoer wordt afgedrukt. De invoerlijst kan alles bevatten wat kan worden weergegeven als tekstuele gegevens, zoals cijfers, getallen, strings, woorden, zinnen, enz. Het scheidingsteken voor invoeritems kan ook een reguliere expressie zijn. Met de regex /[;,]/ kunt u bijvoorbeeld items gebruiken die door komma's of puntkomma's worden gescheiden. De scheidingstekens voor invoer- en uitvoerlijstitems kunnen in de opties worden aangepast. Standaard zijn zowel invoer- als uitvoerlijsten door komma's gescheiden. Listabulous!\",\n      \"title\": \"Wat is een lijstomkeerder?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het roteren van lijstitems. Voer uw lijst in en specificeer de rotatiehoeveelheid om items met een bepaald aantal posities te verschuiven. Perfect voor gegevensmanipulatie, circulaire verschuivingen of het opnieuw ordenen van lijsten.\",\n    \"shortDescription\": \"Lijstitems roteren op opgegeven posities\",\n    \"title\": \"Draaien\"\n  },\n  \"shuffle\": {\n    \"delimiterDescription\": \"Stel een scheidingsteken of reguliere expressie in.\",\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het herschikken van lijstitems. Voer je lijst in en ontvang direct een gerandomiseerde versie met items in willekeurige volgorde. Perfect voor het creëren van variatie, het testen van willekeur of het door elkaar halen van geordende gegevens.\",\n    \"inputListSeparator\": \"Scheidingsteken voor invoerlijst\",\n    \"inputTitle\": \"Invoerlijst\",\n    \"joinSeparatorDescription\": \"Gebruik dit scheidingsteken in de willekeurige lijst.\",\n    \"outputLengthDescription\": \"Geef zoveel willekeurige items weer\",\n    \"resultTitle\": \"Geschudde lijst\",\n    \"shortDescription\": \"De volgorde van lijstitems willekeurig maken\",\n    \"shuffledListLength\": \"Lengte van de geschudde lijst\",\n    \"shuffledListSeparator\": \"Geschudde lijstscheidingsteken\",\n    \"title\": \"Schudden\"\n  },\n  \"sort\": {\n    \"caseSensitive\": \"Hoofdlettergevoelig sorteren\",\n    \"caseSensitiveDescription\": \"Sorteer hoofdletters en kleine letters apart. Hoofdletters gaan vooraf aan kleine letters in een oplopende lijst. (Werkt alleen in alfabetische sorteermodus.)\",\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het sorteren van lijstitems. Voer uw lijst in en specificeer sorteercriteria om items in oplopende of aflopende volgorde te ordenen. Perfect voor gegevensorganisatie, tekstverwerking of het maken van geordende lijsten.\",\n    \"inputItemSeparator\": \"Scheidingsteken voor invoeritems\",\n    \"inputTitle\": \"Invoerlijst\",\n    \"joinSeparatorDescription\": \"Gebruik dit symbool als verbindingsstuk tussen items in een gesorteerde lijst.\",\n    \"orderDescription\": \"Selecteer een sorteervolgorde.\",\n    \"orderOptions\": {\n      \"decreasing\": \"Afnemende volgorde\",\n      \"increasing\": \"Toenemende orde\"\n    },\n    \"removeDuplicates\": \"Duplicaten verwijderen\",\n    \"removeDuplicatesDescription\": \"Verwijder dubbele lijstonderdelen.\",\n    \"resultTitle\": \"Gesorteerde lijst\",\n    \"shortDescription\": \"Lijstitems in de opgegeven volgorde sorteren\",\n    \"sortMethod\": \"Sorteermethode\",\n    \"sortMethodDescription\": \"Selecteer een sorteermethode.\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Alfabetisch sorteren\",\n      \"length\": \"Sorteren op lengte\",\n      \"numeric\": \"Numeriek sorteren\"\n    },\n    \"sortedItemProperties\": \"Gesorteerde itemeigenschappen\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Beperk de items in de invoerlijst met een reguliere expressie.\",\n        \"title\": \"Gebruik een Regex voor het splitsen\"\n      },\n      \"symbol\": {\n        \"description\": \"Beperk de items in de invoerlijst met een teken.\",\n        \"title\": \"Gebruik een symbool voor splitsen\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Stel een scheidingsteken of reguliere expressie in.\",\n    \"title\": \"Soort\"\n  },\n  \"truncate\": {\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het afkappen van lijsten. Voer uw lijst in en specificeer het maximale aantal items dat u wilt behouden. Perfect voor gegevensverwerking, lijstbeheer of het beperken van de lengte van content.\",\n    \"shortDescription\": \"Lijst afkappen tot een bepaald aantal items\",\n    \"title\": \"Afkappen\"\n  },\n  \"unwrap\": {\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het uitpakken van lijstitems. Voer uw ingepakte lijst in en specificeer uitpakcriteria om georganiseerde items plat te maken. Perfect voor gegevensverwerking, tekstmanipulatie of het extraheren van inhoud uit gestructureerde lijsten.\",\n    \"shortDescription\": \"Lijstitems uit gestructureerde opmaak uitpakken\",\n    \"title\": \"Uitpakken\"\n  },\n  \"wrap\": {\n    \"description\": \"Voeg tekst toe voor en na elk item in de lijst.\",\n    \"inputTitle\": \"Invoerlijst\",\n    \"joinSeparatorDescription\": \"Scheidingsteken om de ingepakte lijst samen te voegen\",\n    \"leftTextDescription\": \"Tekst die vóór elk item moet worden toegevoegd\",\n    \"removeEmptyItems\": \"Lege items verwijderen\",\n    \"resultTitle\": \"Ingepakte lijst\",\n    \"rightTextDescription\": \"Tekst die na elk item moet worden toegevoegd\",\n    \"shortDescription\": \"Lijstitems inpakken met opgegeven criteria\",\n    \"splitByRegex\": \"Gesplitst door reguliere expressie\",\n    \"splitBySymbol\": \"Gesplitst op symbool\",\n    \"splitOptions\": \"Gesplitste opties\",\n    \"splitSeparatorDescription\": \"Scheidingsteken om de lijst te splitsen\",\n    \"title\": \"Wrap\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u tekst voor en na elk item in een lijst toevoegen. U kunt verschillende tekst opgeven voor de linker- en rechterkant en bepalen hoe de lijst wordt verwerkt. Deze tool is handig voor het toevoegen van aanhalingstekens, haakjes of andere opmaak aan lijstitems, het voorbereiden van gegevens voor verschillende formaten of het maken van gestructureerde tekst.\",\n      \"title\": \"Lijstomloop\"\n    },\n    \"wrapOptions\": \"Wrap-opties\"\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/number.json",
    "content": "{\n  \"arithmeticSequence\": {\n    \"commonDifferenceDescription\": \"Veelvoorkomend verschil tussen termen (d)\",\n    \"description\": \"Genereer rekenkundige reeksen met aanpasbare parameters.\",\n    \"firstTermDescription\": \"Eerste term van de reeks (a₁)\",\n    \"numberOfTermsDescription\": \"Aantal te genereren termen (n)\",\n    \"outputFormat\": \"Uitvoerformaat\",\n    \"resultTitle\": \"Gegenereerde sequentie\",\n    \"separatorDescription\": \"Scheidingsteken tussen termen\",\n    \"sequenceParameters\": \"Sequentieparameters\",\n    \"shortDescription\": \"Genereer rekenkundige reeksen\",\n    \"title\": \"Rekenkundige reeks\",\n    \"toolInfo\": {\n      \"description\": \"Een rekenkundige rij is een reeks getallen waarbij het verschil tussen elke opeenvolgende term constant is. Dit constante verschil wordt de gemeenschappelijke afwijking genoemd. Gegeven de eerste term (a₁) en de gemeenschappelijke afwijking (d), kan elke term worden gevonden door de gemeenschappelijke afwijking op te tellen bij de vorige term.\",\n      \"title\": \"Wat is een rekenkundige rij?\"\n    }\n  },\n  \"generate\": {\n    \"arithmeticSequenceOption\": \"Optie voor rekenkundige reeks\",\n    \"description\": \"Genereer een getallenreeks met aanpasbare parameters.\",\n    \"numberOfElementsDescription\": \"Aantal elementen in volgorde.\",\n    \"resultTitle\": \"Gegenereerde nummers\",\n    \"separator\": \"Scheidingsteken\",\n    \"separatorDescription\": \"Scheid elementen in de rekenkundige reeks met dit teken.\",\n    \"shortDescription\": \"Genereer willekeurige getallen in opgegeven bereiken\",\n    \"startSequenceDescription\": \"Start de reeks vanaf dit nummer.\",\n    \"stepDescription\": \"Verhoog elk element met dit bedrag\",\n    \"title\": \"Genereren\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u een getallenreeks genereren met aanpasbare parameters. U kunt de startwaarde, stapgrootte en het aantal elementen specificeren.\",\n      \"title\": \"Genereer getallen\"\n    }\n  },\n  \"ohmsLaw\": {\n    \"description\": \"Bereken spanning, stroom en weerstand\",\n    \"longDescription\": \"Deze rekenmachine past de wet van Ohm (V = I × R) toe om een van de drie elektrische parameters te bepalen wanneer de andere twee bekend zijn. De wet van Ohm is een fundamenteel principe in de elektrotechniek dat de relatie beschrijft tussen spanning (V), stroomsterkte (I) en weerstand (R). Deze tool is essentieel voor elektronicahobbyisten, elektrotechnici en studenten die met schakelingen werken om snel onbekende waarden in hun elektrische ontwerpen op te lossen.\",\n    \"shortDescription\": \"Bereken spanning, stroom of weerstand in elektrische circuits met behulp van de wet van Ohm\",\n    \"title\": \"Wet van Ohm\"\n  },\n  \"randomNumberGenerator\": {\n    \"description\": \"Genereer willekeurige getallen binnen een opgegeven bereik met aanpasbare opties.\",\n    \"error\": {\n      \"generationFailed\": \"Het genereren van willekeurige getallen is mislukt. Controleer uw invoerparameters.\"\n    },\n    \"info\": {\n      \"description\": \"Een willekeurige getallengenerator genereert onvoorspelbare getallen binnen een bepaald bereik. Deze tool maakt gebruik van cryptografisch veilige willekeurige getallengeneratie om echt willekeurige resultaten te garanderen. Handig voor simulaties, games, statistische steekproeven en testscenario's.\",\n      \"title\": \"Wat is een willekeurige getallengenerator?\"\n    },\n    \"longDescription\": \"Genereer willekeurige getallen binnen een bepaald bereik met opties voor gehele getallen of decimalen, het toestaan of voorkomen van duplicaten en het sorteren van resultaten. Perfect voor simulaties, tests, games en statistische analyses.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDecimals\": {\n          \"description\": \"Genereer decimale getallen in plaats van gehele getallen\",\n          \"title\": \"Decimale getallen toestaan\"\n        },\n        \"allowDuplicates\": {\n          \"description\": \"Laat hetzelfde nummer meerdere keren verschijnen\",\n          \"title\": \"Duplicaten toestaan\"\n        },\n        \"countDescription\": \"Aantal willekeurige getallen dat gegenereerd moet worden (1-10.000)\",\n        \"sortResults\": {\n          \"description\": \"Sorteer de gegenereerde getallen in oplopende volgorde\",\n          \"title\": \"Resultaten sorteren\"\n        },\n        \"title\": \"Generatieopties\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Teken(s) om de gegenereerde getallen te scheiden\",\n        \"title\": \"Uitvoerinstellingen\"\n      },\n      \"range\": {\n        \"maxDescription\": \"Maximale waarde (inclusief)\",\n        \"minDescription\": \"Minimumwaarde (inclusief)\",\n        \"title\": \"Bereikinstellingen\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Graaf\",\n      \"hasDuplicates\": \"Bevat duplicaten\",\n      \"isSorted\": \"Gesorteerd\",\n      \"range\": \"Bereik\",\n      \"title\": \"Gegenereerde willekeurige getallen\"\n    },\n    \"shortDescription\": \"Genereer willekeurige getallen in aangepaste bereiken\",\n    \"title\": \"Willekeurige nummergenerator\"\n  },\n  \"randomPortGenerator\": {\n    \"description\": \"Genereer willekeurige netwerkpoorten binnen opgegeven bereiken met aanpasbare opties.\",\n    \"error\": {\n      \"generationFailed\": \"Het genereren van willekeurige poorten is mislukt. Controleer uw invoerparameters.\"\n    },\n    \"info\": {\n      \"description\": \"Een willekeurige poortgenerator genereert onvoorspelbare netwerkpoortnummers binnen gespecificeerde bereiken. Deze tool volgt de IANA-poortnummerstandaarden en omvat identificatie van veelgebruikte services. Handig voor ontwikkeling, testen, netwerkconfiguratie en het voorkomen van poortconflicten.\",\n      \"title\": \"Wat is een willekeurige poortgenerator?\"\n    },\n    \"longDescription\": \"Genereer willekeurige netwerkpoorten binnen opgegeven bereiken (bekend, geregistreerd, dynamisch of aangepast). Perfect voor ontwikkeling, testen en netwerkconfiguratie. Inclusief poortservice-identificatie voor veelgebruikte poorten.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDuplicates\": {\n          \"description\": \"Sta toe dat dezelfde poort meerdere keren verschijnt\",\n          \"title\": \"Duplicaten toestaan\"\n        },\n        \"countDescription\": \"Aantal willekeurige poorten om te genereren (1-1.000)\",\n        \"sortResults\": {\n          \"description\": \"Sorteer de gegenereerde poorten in oplopende volgorde\",\n          \"title\": \"Resultaten sorteren\"\n        },\n        \"title\": \"Generatieopties\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Teken(s) om de gegenereerde poorten te scheiden\",\n        \"title\": \"Uitvoerinstellingen\"\n      },\n      \"range\": {\n        \"custom\": \"Aangepast bereik\",\n        \"dynamic\": \"Dynamische poorten (49152-65535)\",\n        \"maxPortDescription\": \"Maximaal poortnummer (1-65535)\",\n        \"minPortDescription\": \"Minimum poortnummer (1-65535)\",\n        \"registered\": \"Geregistreerde havens (1024-49151)\",\n        \"title\": \"Poortbereikinstellingen\",\n        \"wellKnown\": \"Bekende havens (1-1023)\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Graaf\",\n      \"hasDuplicates\": \"Bevat duplicaten\",\n      \"isSorted\": \"Gesorteerd\",\n      \"portDetails\": \"Havendetails\",\n      \"range\": \"Poortbereik\",\n      \"title\": \"Gegenereerde willekeurige poorten\"\n    },\n    \"shortDescription\": \"Genereer willekeurige netwerkpoorten\",\n    \"title\": \"Willekeurige poortgenerator\"\n  },\n  \"slackline\": {\n    \"description\": \"Bereken de spanning in een slackline\",\n    \"longDescription\": \"Deze rekenmachine gaat uit van een last in het midden van het touw\",\n    \"shortDescription\": \"Bereken de geschatte spanning van een slackline of waslijn. Vertrouw hier niet op voor je veiligheid.\",\n    \"title\": \"Slackline-spanning\"\n  },\n  \"sphereArea\": {\n    \"description\": \"Oppervlakte van een bol\",\n    \"longDescription\": \"Deze rekenmachine bepaalt de oppervlakte van een bol met behulp van de formule A = 4πr². U kunt de straal invoeren om de oppervlakte te berekenen, of de oppervlakte invoeren om de gewenste straal te berekenen. Deze tool is handig voor studenten meetkunde, ingenieurs die met bolvormige objecten werken en iedereen die berekeningen met bolvormige oppervlakken moet uitvoeren.\",\n    \"shortDescription\": \"Bereken het oppervlak van een bol op basis van de straal\",\n    \"title\": \"Oppervlakte van een bol\"\n  },\n  \"sphereVolume\": {\n    \"description\": \"Volume van een bol\",\n    \"longDescription\": \"Deze rekenmachine berekent het volume van een bol met de formule V = (4/3)πr³. U kunt de straal of diameter invoeren om het volume te berekenen, of het volume invoeren om de gewenste straal te bepalen. Deze tool is nuttig voor studenten, ingenieurs en professionals die met bolvormige objecten werken in vakgebieden zoals natuurkunde, techniek en productie.\",\n    \"shortDescription\": \"Bereken het volume van een bol met behulp van de straal of diameter\",\n    \"title\": \"Volume van een bol\"\n  },\n  \"sum\": {\n    \"description\": \"Bereken de som van een lijst met getallen. Voer getallen gescheiden door komma's of nieuwe regels in om de som te berekenen.\",\n    \"example1Description\": \"In dit voorbeeld berekenen we de som van tien positieve gehele getallen. Deze gehele getallen worden weergegeven als een kolom en hun totale som is gelijk aan 19494.\",\n    \"example1Title\": \"Som van tien positieve getallen\",\n    \"example2Description\": \"In dit voorbeeld wordt een kolom met drieëntwintig zelfstandige naamwoorden van drie lettergrepen omgedraaid en worden alle woorden van onder naar boven afgedrukt. Om de items in de lijst te scheiden, wordt het teken \\\\n gebruikt als scheidingsteken tussen de invoeritems. Dit betekent dat elk item op een aparte regel staat.\",\n    \"example2Title\": \"Tel bomen in het park\",\n    \"example3Description\": \"In dit voorbeeld tellen we negentig verschillende waarden bij elkaar op: positieve getallen, negatieve getallen, gehele getallen en decimale breuken. We zetten een komma als scheidingsteken in de invoer en na het optellen krijgen we 0 als uitvoer.\",\n    \"example3Title\": \"Som van gehele getallen en decimalen\",\n    \"example4Description\": \"In dit voorbeeld berekenen we de som van alle tien cijfers en schakelen we de optie \\\"Print Running Sum\\\" in. De tussenliggende waarden van de som verkrijgen we door optellen. Zo krijgen we de volgende reeks in de uitvoer: 0, 1 (0 + 1), 3 (0 + 1 + 2), 6 (0 + 1 + 2 + 3), 10 (0 + 1 + 2 + 3 + 4), enzovoort.\",\n    \"example4Title\": \"Doorlopende som van getallen\",\n    \"extractionTypes\": {\n      \"delimiter\": {\n        \"description\": \"Pas hier het scheidingsteken voor getallen aan. (Standaard een regelafbreking.)\",\n        \"title\": \"Nummerscheidingsteken\"\n      },\n      \"smart\": {\n        \"description\": \"Automatische detectie van getallen in de invoer.\",\n        \"title\": \"Slimme Som\"\n      }\n    },\n    \"inputTitle\": \"Invoer\",\n    \"numberExtraction\": \"Nummer extractie\",\n    \"printRunningSum\": \"Afdrukken Lopende Som\",\n    \"printRunningSumDescription\": \"Geef de som weer terwijl deze stap voor stap wordt berekend.\",\n    \"resultTitle\": \"Totaal\",\n    \"runningSum\": \"Lopende som\",\n    \"shortDescription\": \"Bereken de som van getallen\",\n    \"title\": \"Som\",\n    \"toolInfo\": {\n      \"description\": \"Dit is een online browserprogramma waarmee je de som van een aantal getallen kunt berekenen. Je kunt de getallen invoeren, gescheiden door een komma, spatie of een ander teken, inclusief de regelafbreking. Je kunt ook gewoon een fragment tekstuele gegevens plakken met numerieke waarden die je wilt optellen. Het programma zal deze waarden dan extraheren en de som bepalen.\",\n      \"title\": \"Wat is een getallensomcalculator?\"\n    }\n  },\n  \"voltageDropInWire\": {\n    \"description\": \"Bereken de retourspanning en het vermogensverlies in een 2-aderige kabel\",\n    \"longDescription\": \"Deze calculator helpt bij het bepalen van de spanningsval en het vermogensverlies in een tweeaderige elektrische kabel. Hierbij wordt rekening gehouden met de kabellengte, de draaddikte (dwarsdoorsnede), de materiaalweerstand en de stroomsterkte. De tool berekent de spanningsval heen en terug, de totale weerstand van de kabel en het vermogen dat als warmte wordt afgegeven. Dit is met name handig voor elektrotechnici, elektriciens en hobbyisten bij het ontwerpen van elektrische systemen om ervoor te zorgen dat de spanningsniveaus bij de belasting binnen acceptabele grenzen blijven.\",\n    \"shortDescription\": \"Bereken spanningsval en vermogensverlies in elektrische kabels op basis van lengte, materiaal en stroom\",\n    \"title\": \"Spanningsval in de kabel bij retour\"\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/pdf.json",
    "content": "{\n  \"compressPdf\": {\n    \"compressedFileSize\": \"Gecomprimeerde bestandsgrootte\",\n    \"compressingPdf\": \"PDF comprimeren...\",\n    \"compressionLevel\": \"Compressieniveau\",\n    \"compressionSettings\": \"Compressie-instellingen\",\n    \"description\": \"Verklein de PDF-bestandsgrootte met behoud van kwaliteit met Ghostscript\",\n    \"errorCompressingPdf\": \"PDF comprimeren is mislukt: {{error}}\",\n    \"errorReadingPdf\": \"PDF-bestand kan niet worden gelezen. Controleer of het een geldig PDF-bestand is.\",\n    \"fileSize\": \"Originele bestandsgrootte\",\n    \"highCompression\": \"Hoge compressie\",\n    \"highCompressionDescription\": \"Maximale bestandsgroottevermindering met enig kwaliteitsverlies\",\n    \"inputTitle\": \"PDF-invoer\",\n    \"longDescription\": \"Comprimeer PDF-bestanden veilig in uw browser met Ghostscript. Uw bestanden verlaten uw apparaat nooit, waardoor volledige privacy wordt gegarandeerd en de bestandsgrootte wordt verkleind voor e-maildeling, uploaden naar websites en het besparen van opslagruimte. Mogelijk gemaakt door WebAssembly-technologie.\",\n    \"lowCompression\": \"Lage compressie\",\n    \"lowCompressionDescription\": \"Verklein de bestandsgrootte lichtjes met minimaal kwaliteitsverlies\",\n    \"mediumCompression\": \"Gemiddelde compressie\",\n    \"mediumCompressionDescription\": \"Balans tussen bestandsgrootte en kwaliteit\",\n    \"pages\": \"Aantal pagina's\",\n    \"resultTitle\": \"Gecomprimeerde PDF\",\n    \"shortDescription\": \"Comprimeer PDF-bestanden veilig in uw browser\",\n    \"title\": \"PDF comprimeren\"\n  },\n  \"editor\": {\n    \"description\": \"Geavanceerde PDF-editor met mogelijkheden voor annotatie, formulieren invullen, markeren en exporteren. Bewerk uw PDF's rechtstreeks in de browser met professionele tools zoals tekst invoegen, tekenen, markeren, ondertekenen en formulieren invullen.\",\n    \"shortDescription\": \"Bewerk PDF's met geavanceerde hulpmiddelen voor annotatie, ondertekening en bewerking\",\n    \"title\": \"PDF-editor\"\n  },\n  \"merge\": {\n    \"inputTitle\": \"PDF-invoer\",\n    \"loadingText\": \"Pagina's extraheren\",\n    \"resultTitle\": \"Samengevoegde PDF-uitvoer\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kun je meerdere PDF-bestanden samenvoegen tot één document. Om de tool te gebruiken, upload je eenvoudig de PDF-bestanden die je wilt samenvoegen. De tool combineert vervolgens alle pagina's uit de invoerbestanden tot één PDF-document.\",\n      \"title\": \"Hoe gebruik ik de PDF-samenvoegtool?\"\n    }\n  },\n  \"mergePdf\": {\n    \"description\": \"Combineer meerdere PDF-bestanden tot één document.\",\n    \"inputTitle\": \"PDF-invoer\",\n    \"mergingPdfs\": \"PDF's samenvoegen\",\n    \"pdfOptions\": \"PDF-opties\",\n    \"resultTitle\": \"Samengevoegde PDF\",\n    \"shortDescription\": \"Meerdere PDF-bestanden samenvoegen tot één document\",\n    \"sortByFileName\": \"Sorteren op bestandsnaam\",\n    \"sortByFileNameDescription\": \"PDF's alfabetisch sorteren op bestandsnaam\",\n    \"sortByUploadOrder\": \"Sorteren op uploadvolgorde\",\n    \"sortByUploadOrderDescription\": \"Bewaar PDF's in de volgorde waarin ze zijn geüpload\",\n    \"title\": \"PDF samenvoegen\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kun je meerdere PDF-bestanden combineren tot één document. Je kunt kiezen hoe je de PDF's wilt sorteren en de tool voegt ze samen in de opgegeven volgorde.\",\n      \"title\": \"PDF-bestanden samenvoegen\"\n    }\n  },\n  \"pdfToEpub\": {\n    \"description\": \"Transformeer PDF-documenten naar EPUB-bestanden voor betere compatibiliteit met e-readers.\",\n    \"shortDescription\": \"PDF-bestanden converteren naar EPUB-formaat\",\n    \"title\": \"PDF naar EPUB\"\n  },\n  \"pdfToPng\": {\n    \"description\": \"Transformeer PDF-documenten naar PNG-panelen.\",\n    \"longDescription\": \"Upload een PDF en converteer elke pagina rechtstreeks in je browser naar een hoogwaardige PNG-afbeelding. Deze tool is ideaal voor het extraheren van visuele content of het delen van individuele pagina's. Er worden geen gegevens geüpload - alles wordt lokaal uitgevoerd.\",\n    \"shortDescription\": \"PDF naar PNG-afbeeldingen converteren\",\n    \"title\": \"PDF naar PNG\"\n  },\n  \"convertToPdf\": {\n    \"title\": \"Afbeeldingen naar PDF\",\n    \"description\": \"Verschillende afbeeldingsformaten (PNG, GIF, JPG, TIF, PSD, SVG, WEBP, HEIC, RAW) naar PDF converteren, met opties om de afbeelding te schalen en de paginaoriëntatie te kiezen.\",\n    \"shortDescription\": \"Afbeeldingen naar PDF converteren met schaal- en oriëntatiecontrole\"\n  },\n  \"protectPdf\": {\n    \"description\": \"Voeg wachtwoordbeveiliging toe aan uw PDF-bestanden veilig in uw browser\",\n    \"shortDescription\": \"PDF-bestanden veilig met een wachtwoord beveiligen\",\n    \"title\": \"PDF beschermen\"\n  },\n  \"rotatePdf\": {\n    \"allPagesWillBeRotated\": \"Alle {{count}} pagina's worden gedraaid\",\n    \"angleOptions\": {\n      \"clockwise90\": \"90° met de klok mee\",\n      \"counterClockwise270\": \"270° (90° tegen de klok in)\",\n      \"upsideDown180\": \"180° (op zijn kop)\"\n    },\n    \"applyToAllPages\": \"Toepassen op alle pagina's\",\n    \"description\": \"Pagina's in een PDF-document roteren.\",\n    \"inputTitle\": \"PDF-invoer\",\n    \"longDescription\": \"Wijzig de oriëntatie van PDF-pagina's door ze 90, 180 of 270 graden te draaien. Handig voor het corrigeren van onjuist gescande documenten of het voorbereiden van PDF's voor afdrukken.\",\n    \"pageRangesDescription\": \"Voer paginanummers of bereiken in, gescheiden door komma's (bijv. 1, 3, 5-7)\",\n    \"pageRangesPlaceholder\": \"bijv. 1,5-8\",\n    \"pagesWillBeRotated\": \"{{count}} pagina{{count !== 1 ? 's' : ''}} zal worden gedraaid\",\n    \"pdfPageCount\": \"PDF heeft {{count}} pagina{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"Gedraaide PDF\",\n    \"rotatingPages\": \"Roterende pagina's\",\n    \"rotationAngle\": \"Rotatiehoek\",\n    \"rotationSettings\": \"Rotatie-instellingen\",\n    \"shortDescription\": \"Pagina's in een PDF-document roteren\",\n    \"title\": \"PDF roteren\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u pagina's in een PDF-document roteren. U kunt alle pagina's roteren of specifieke pagina's opgeven. Kies een rotatiehoek: 90° met de klok mee, 180° (ondersteboven) of 270° (90° tegen de klok in). Om specifieke pagina's te roteren, schakelt u 'Toepassen op alle pagina's' uit en voert u paginanummers of paginabereiken in, gescheiden door komma's (bijvoorbeeld 1, 3, 5-7).\",\n      \"title\": \"De PDF-rotatietool gebruiken\"\n    }\n  },\n  \"splitPdf\": {\n    \"description\": \"Specifieke pagina's uit een PDF-document extraheren.\",\n    \"extractingPages\": \"Pagina's extraheren\",\n    \"inputTitle\": \"PDF-invoer\",\n    \"pageExtractionPreview\": \"{{count}} pagina{{count !== 1 ? 's' : ''}} zal worden geëxtraheerd\",\n    \"pageRangesDescription\": \"Voer paginanummers of bereiken in, gescheiden door komma's (bijv. 1, 3, 5-7)\",\n    \"pageRangesPlaceholder\": \"bijv. 1,5-8\",\n    \"pageSelection\": \"Paginaselectie\",\n    \"pdfPageCount\": \"PDF heeft {{count}} pagina{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"Geëxtraheerde PDF\",\n    \"shortDescription\": \"Specifieke pagina's uit een PDF-bestand extraheren\",\n    \"title\": \"PDF splitsen\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u specifieke pagina's uit een PDF-document extraheren. U kunt specifieke pagina's of paginareeksen opgeven die u wilt extraheren.\",\n      \"title\": \"PDF splitsen\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/string.json",
    "content": "{\n  \"base64\": {\n    \"decode\": \"Base64-decodering\",\n    \"description\": \"Codeer of decodeer tekst met Base64-codering.\",\n    \"encode\": \"Base64-codering\",\n    \"inputTitle\": \"Invoergegevens\",\n    \"optionsTitle\": \"Base64-opties\",\n    \"resultTitle\": \"Resultaat\",\n    \"shortDescription\": \"Codeer of decodeer gegevens met Base64.\",\n    \"title\": \"Base64-encoder/-decoder\",\n    \"toolInfo\": {\n      \"description\": \"Base64 is een coderingsmethode die gegevens in een ASCII-stringformaat weergeeft door ze te vertalen naar een radix-64-representatie. Hoewel het gebruikt kan worden om strings te coderen, wordt het vaak gebruikt om binaire gegevens te coderen voor transmissie via media die ontworpen zijn voor tekstuele gegevens.\",\n      \"title\": \"Wat is Base64?\"\n    }\n  },\n  \"censor\": {\n    \"description\": \"Hulpprogramma voor het censureren van woorden in tekst. Laad uw tekst in het invoerformulier aan de linkerkant, specificeer alle ongewenste woorden in de opties en u krijgt direct gecensureerde tekst in het uitvoergebied.\\\", longDescription: 'Met deze online tool kunt u bepaalde woorden in elke tekst censureren. U kunt een lijst met ongewenste woorden (zoals scheldwoorden of geheime woorden) opgeven en het programma vervangt deze door alternatieve woorden en creëert een veilig leesbare tekst. De woorden kunnen worden opgegeven in een tekstveld van meerdere regels in de opties door één woord per regel in te voeren.', keywords: ['tekst', 'censureren', 'woorden', 'tekens'], component: lazy(() => import('./index')), i18n: { name: 'string:censor.title', description: 'string:censor.description\",\n    \"shortDescription\": \"Maskeer snel slechte woorden of vervang ze door alternatieve woorden.\",\n    \"title\": \"Tekstcensor\"\n  },\n  \"createPalindrome\": {\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde tool voor het maken van palindromen van elke tekst. Voer tekst in en transformeer deze direct in een palindroom die zowel van voor naar achter als van achter naar voren hetzelfde leest. Perfect voor woordspelletjes, het creëren van symmetrische tekstpatronen of het verkennen van taalkundige curiosa.\",\n    \"shortDescription\": \"Maak een tekst die van voor naar achter en van achter naar voren hetzelfde leest\",\n    \"title\": \"Palindroom maken\"\n  },\n  \"extractSubstring\": {\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het extraheren van substrings uit tekst. Voer uw tekst in en specificeer de begin- en eindposities om het gewenste deel te extraheren. Perfect voor gegevensverwerking, tekstanalyse of het extraheren van specifieke inhoud uit grotere tekstblokken.\",\n    \"shortDescription\": \"Een tekstgedeelte tussen opgegeven posities extraheren\",\n    \"title\": \"Substring extraheren\"\n  },\n  \"hiddenCharacterDetector\": {\n    \"analysisOptions\": \"Analyse-opties\",\n    \"category\": \"Categorie\",\n    \"description\": \"Detecteer verborgen Unicode-tekens, met name RTL-override-tekens die bij aanvallen kunnen worden gebruikt.\",\n    \"foundChars\": \"Gevonden {{count}} verborgen teken(s):\",\n    \"inputPlaceholder\": \"Voer tekst in om te controleren op verborgen tekens...\",\n    \"inputTitle\": \"Te analyseren tekst\",\n    \"invisibleChar\": \"Onzichtbaar personage\",\n    \"invisibleFound\": \"Onzichtbare karakters gevonden\",\n    \"longDescription\": \"Deze tool helpt u verborgen Unicode-tekens in tekst te detecteren, met name RTL-override-tekens (Right-to-Left) die bij aanvallen kunnen worden gebruikt. De tool kan onzichtbare tekens, tekens met een breedte van nul en andere potentieel schadelijke Unicode-reeksen identificeren die mogelijk verborgen zijn in ogenschijnlijk onschuldige tekst.\",\n    \"noHiddenChars\": \"Er zijn geen verborgen tekens in de tekst gedetecteerd.\",\n    \"optionsDescription\": \"Configureer welke typen verborgen tekens moeten worden gedetecteerd en hoe de resultaten moeten worden weergegeven.\",\n    \"position\": \"Positie\",\n    \"rtlAlert\": \"⚠️ RTL Override-tekens gedetecteerd! Deze tekst kan schadelijke verborgen tekens bevatten.\",\n    \"rtlFound\": \"RTL Override gevonden\",\n    \"rtlOverride\": \"RTL-override-teken\",\n    \"rtlWarning\": \"WAARSCHUWING: RTL-override-tekens gedetecteerd! Deze kunnen worden gebruikt bij aanvallen.\",\n    \"shortDescription\": \"Verborgen Unicode-tekens in tekst vinden\",\n    \"summary\": \"Analyse Samenvatting\",\n    \"title\": \"Verborgen karakterdetector\",\n    \"totalChars\": \"Totaal aantal verborgen tekens: {{count}}\",\n    \"unicode\": \"Unicode\",\n    \"zeroWidthChar\": \"Teken met nulbreedte\",\n    \"zeroWidthFound\": \"Er zijn tekens met een breedte van nul gevonden\"\n  },\n  \"join\": {\n    \"blankLinesAndTrailingSpaces\": \"Lege regels en spaties aan het einde\",\n    \"deleteBlankDescription\": \"Verwijder regels die geen tekstsymbolen bevatten.\",\n    \"deleteBlankTitle\": \"Lege regels verwijderen\",\n    \"deleteTrailingDescription\": \"Verwijder spaties en tabs aan het einde van de regels.\",\n    \"deleteTrailingTitle\": \"Verwijder afsluitende spaties\",\n    \"description\": \"Voeg tekststukken samen met aanpasbare scheidingstekens.\",\n    \"inputTitle\": \"Tekststukken\",\n    \"joinCharacterDescription\": \"Symbool dat gebroken tekststukken met elkaar verbindt. (Standaard spatie.)\",\n    \"joinCharacterPlaceholder\": \"Sluit je aan bij het personage\",\n    \"resultTitle\": \"Gekoppelde tekst\",\n    \"shortDescription\": \"Tekstelementen samenvoegen met een opgegeven scheidingsteken\",\n    \"textMergedOptions\": \"Opties voor samengevoegde tekst\",\n    \"title\": \"Tekst toevoegen\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kun je tekstdelen samenvoegen. Het neemt een lijst met tekstwaarden, gescheiden door nieuwe regels, en voegt deze samen. Je kunt het teken instellen dat tussen de delen van de samengevoegde tekst wordt geplaatst. Je kunt ook alle lege regels negeren en spaties en tabs aan het einde van alle regels verwijderen. Tekstabulous!\",\n      \"title\": \"Wat is een tekstsamenvoeger?\"\n    }\n  },\n  \"palindrome\": {\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma om te controleren of tekst een palindroom is. Controleer direct of uw tekst van voor naar achter hetzelfde leest. Perfect voor woordpuzzels, taalkundige analyses of het valideren van symmetrische tekstpatronen. Ondersteunt diverse scheidingstekens en palindroomdetectie van meerdere woorden.\",\n    \"shortDescription\": \"Controleren of de tekst van voor naar achter hetzelfde leest\",\n    \"title\": \"Palindroom\"\n  },\n  \"passwordGenerator\": {\n    \"avoidAmbiguous\": \"Vermijd dubbelzinnige tekens (i, I, l, 0, O)\",\n    \"description\": \"Genereer veilige, willekeurige wachtwoorden met aanpasbare lengte en tekentypes. Kies uit kleine letters, hoofdletters, cijfers en speciale tekens. Optie om dubbelzinnige tekens te vermijden voor een betere leesbaarheid.\",\n    \"includeLowercase\": \"Gebruik kleine letters (a-z)\",\n    \"includeNumbers\": \"Inclusief cijfers (0-9)\",\n    \"includeSymbols\": \"Speciale tekens toevoegen\",\n    \"includeUppercase\": \"Inclusief hoofdletters (A-Z)\",\n    \"lengthDesc\": \"Lengte van het wachtwoord\",\n    \"lengthPlaceholder\": \"bijv. 12\",\n    \"optionsTitle\": \"Wachtwoordopties\",\n    \"resultTitle\": \"gegenereerd wachtwoord\",\n    \"shortDescription\": \"Genereer veilige willekeurige wachtwoorden met aangepaste opties\",\n    \"title\": \"Wachtwoordgenerator\",\n    \"toolInfo\": {\n      \"description\": \"Deze tool genereert veilige, willekeurige wachtwoorden op basis van de door u geselecteerde criteria. U kunt de lengte aanpassen, verschillende tekentypen toevoegen of uitsluiten en dubbelzinnige tekens vermijden voor een betere leesbaarheid. Perfect voor het maken van sterke wachtwoorden voor accounts, applicaties of andere beveiligingsbehoeften.\",\n      \"title\": \"Over wachtwoordgenerator\"\n    }\n  },\n  \"quote\": {\n    \"allowDoubleQuotation\": \"Dubbele aanhalingstekens toestaan\",\n    \"description\": \"Voeg aanhalingstekens toe rondom de tekst met aanpasbare opties.\",\n    \"inputTitle\": \"Invoertekst\",\n    \"leftQuoteDescription\": \"Linker aanhalingsteken(s)\",\n    \"processAsMultiLine\": \"Verwerken als meerregelige tekst\",\n    \"quoteEmptyLines\": \"Lege regels citeren\",\n    \"quoteOptions\": \"Offerte-opties\",\n    \"resultTitle\": \"Geciteerde tekst\",\n    \"rightQuoteDescription\": \"Rechter aanhalingsteken(s)\",\n    \"shortDescription\": \"Voeg aanhalingstekens toe rond tekst met verschillende stijlen\",\n    \"title\": \"Tekstcitaat\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kun je aanhalingstekens rond tekst plaatsen. Je kunt verschillende aanhalingstekens kiezen, tekst van meerdere regels verwerken en bepalen hoe lege regels worden verwerkt. Het is handig om tekst voor te bereiden voor programmering, gegevens op te maken of gestileerde tekst te maken.\",\n      \"title\": \"Tekstcitaat\"\n    }\n  },\n  \"randomizeCase\": {\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het willekeurig maken van teksthoofdletters en kleine letters. Voer je tekst in en transformeer deze direct met willekeurige hoofdletters en kleine letters. Perfect voor het creëren van unieke teksteffecten, het testen van hoofdlettergevoeligheid of het genereren van gevarieerde tekstpatronen.\",\n    \"shortDescription\": \"Willekeurige hoofdlettergebruik in tekst\",\n    \"title\": \"Randomiseer het geval\"\n  },\n  \"removeDuplicateLines\": {\n    \"description\": \"Laad je tekst in het invoerformulier aan de linkerkant en je krijgt direct tekst zonder dubbele regels in het uitvoergebied. Krachtig, gratis en snel. Laad tekstregels – krijg unieke tekstregels.\",\n    \"shortDescription\": \"Verwijder snel alle herhaalde regels uit de tekst\",\n    \"title\": \"Dubbele regels verwijderen\"\n  },\n  \"repeat\": {\n    \"delimiterDescription\": \"Scheidingsteken voor uitvoerkopieën.\",\n    \"delimiterPlaceholder\": \"scheidingsteken\",\n    \"description\": \"Herhaal tekst meerdere keren met aanpasbare scheidingstekens.\",\n    \"inputTitle\": \"Invoertekst\",\n    \"numberPlaceholder\": \"Nummer\",\n    \"repeatAmountDescription\": \"Aantal herhalingen.\",\n    \"repetitionsDelimiter\": \"Herhalingen scheidingsteken\",\n    \"resultTitle\": \"Herhaalde tekst\",\n    \"shortDescription\": \"Herhaal de tekst meerdere keren\",\n    \"textRepetitions\": \"Tekstherhalingen\",\n    \"title\": \"Herhaal tekst\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u een bepaalde tekst meerdere keren herhalen met een optionele scheidingsteken.\",\n      \"title\": \"Herhaal tekst\"\n    }\n  },\n  \"reverse\": {\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het omkeren van tekst. Voer tekst in en laat deze direct omkeren, teken voor teken. Perfect voor het maken van spiegeltekst, het analyseren van palindromen of het experimenteren met tekstpatronen. Spaties en speciale tekens blijven behouden tijdens het omkeren.\",\n    \"inputTitle\": \"Omgekeerde tekst\",\n    \"processMultiLine\": \"Verwerk meerregelige tekst\",\n    \"processMultiLineDescription\": \"Elke regel wordt onafhankelijk omgedraaid\",\n    \"resultTitle\": \"Omgekeerde tekst\",\n    \"reversalOptions\": \"Omkeeropties\",\n    \"shortDescription\": \"Elke tekst teken voor teken omdraaien\",\n    \"skipEmptyLines\": \"Lege regels overslaan\",\n    \"skipEmptyLinesDescription\": \"Lege regels worden uit de uitvoer verwijderd\",\n    \"title\": \"Achteruit\",\n    \"trimWhitespace\": \"Witruimte bijsnijden\",\n    \"trimWhitespaceDescription\": \"Verwijder voorloop- en volgspaties van elke regel\"\n  },\n  \"rot13\": {\n    \"description\": \"Codeer of decodeer tekst met behulp van de ROT13-codering.\",\n    \"inputTitle\": \"Invoertekst\",\n    \"resultTitle\": \"ROT13-resultaat\",\n    \"shortDescription\": \"Codeer of decodeer tekst met behulp van de ROT13-codering.\",\n    \"title\": \"ROT13 Encoder/Decoder\",\n    \"toolInfo\": {\n      \"description\": \"ROT13 (rotate by 13 places) is een eenvoudige lettervervangingscode die een letter vervangt door de 13e letter erna in het alfabet. ROT13 is een speciaal geval van de Caesarcode, die in het oude Rome werd ontwikkeld. Omdat het Engelse alfabet 26 letters telt, is ROT13 zijn eigen inverse; dat wil zeggen, om ROT13 ongedaan te maken, wordt hetzelfde algoritme toegepast, zodat dezelfde actie kan worden gebruikt voor zowel coderen als decoderen.\",\n      \"title\": \"Wat is ROT13?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"Draai tekens in tekst over opgegeven posities.\",\n    \"inputTitle\": \"Invoertekst\",\n    \"processAsMultiLine\": \"Verwerken als meerregelige tekst (elke regel afzonderlijk roteren)\",\n    \"resultTitle\": \"Gedraaide tekst\",\n    \"rotateLeft\": \"Linksom draaien\",\n    \"rotateRight\": \"Rechts draaien\",\n    \"rotationOptions\": \"Rotatieopties\",\n    \"shortDescription\": \"Verplaats tekens in tekst per positie.\",\n    \"stepDescription\": \"Aantal te roteren posities\",\n    \"title\": \"Tekst roteren\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u tekens in een tekenreeks met een bepaald aantal posities roteren. U kunt naar links of rechts roteren en tekst van meerdere regels verwerken door elke regel afzonderlijk te roteren. Tekenreeksrotatie is handig voor eenvoudige teksttransformaties, het maken van patronen of het implementeren van basisversleutelingstechnieken.\",\n      \"title\": \"Snaarrotatie\"\n    }\n  },\n  \"split\": {\n    \"charAfterChunkDescription\": \"Karakter na elk stukje\",\n    \"charBeforeChunkDescription\": \"Karakter voor elk stukje\",\n    \"chunksDescription\": \"Aantal gelijke\\nlengtes in de uitvoer.\",\n    \"chunksTitle\": \"Gebruik een aantal stukken\",\n    \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het splitsen van tekst. Voer uw tekst in en geef een scheidingsteken op om deze in meerdere delen te splitsen. Perfect voor gegevensverwerking, tekstmanipulatie of het extraheren van specifieke inhoud uit grotere tekstblokken.\",\n    \"lengthDescription\": \"Aantal symbolen dat in elk uitvoerblok wordt geplaatst.\",\n    \"lengthTitle\": \"Gebruik lengte voor splitsen\",\n    \"outputSeparatorDescription\": \"Teken dat tussen de gesplitste delen wordt geplaatst.\\n(Standaard is dit een nieuwe regel \\\"\\\\n\\\".)\",\n    \"outputSeparatorOptions\": \"Opties voor uitvoerscheidingstekens\",\n    \"regexDescription\": \"Reguliere expressie die wordt gebruikt om tekst in stukken te splitsen.\\n(Standaard meerdere spaties.)\",\n    \"regexTitle\": \"Gebruik een Regex voor het splitsen\",\n    \"resultTitle\": \"Tekststukken\",\n    \"shortDescription\": \"Tekst in meerdere delen splitsen met behulp van een scheidingsteken\",\n    \"splitSeparatorOptions\": \"Opties voor scheidingslijnen\",\n    \"symbolDescription\": \"Teken dat wordt gebruikt om tekst in stukken te splitsen.\\n(Standaard spatie.)\",\n    \"symbolTitle\": \"Gebruik een symbool voor splitsen\",\n    \"title\": \"Split\"\n  },\n  \"statistic\": {\n    \"characterFrequencyAnalysis\": \"Karakterfrequentieanalyse\",\n    \"characterFrequencyAnalysisDescription\": \"Tel hoe vaak elk teken in de tekst voorkomt\",\n    \"delimitersOptions\": \"Opties voor scheidingstekens\",\n    \"description\": \"Analyseer tekst en genereer uitgebreide statistieken.\",\n    \"includeEmptyLines\": \"Lege regels toevoegen\",\n    \"includeEmptyLinesDescription\": \"Voeg lege regels toe bij het tellen van regels\",\n    \"inputTitle\": \"Invoertekst\",\n    \"resultTitle\": \"Tekststatistieken\",\n    \"sentenceDelimitersDescription\": \"Voer aangepaste tekens in die u wilt gebruiken om zinnen in uw taal af te bakenen (gescheiden door komma's) of laat dit veld leeg voor de standaardinstelling.\",\n    \"sentenceDelimitersPlaceholder\": \"bijv. ., !, ?, ...\",\n    \"shortDescription\": \"Ontvang statistieken over uw tekst\",\n    \"statisticsOptions\": \"Statistiekenopties\",\n    \"title\": \"Tekststatistieken\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u tekst analyseren en uitgebreide statistieken genereren, waaronder het aantal tekens, woorden, regels en de frequentieanalyse van tekens en woorden.\",\n      \"title\": \"Wat is een {{title}}?\"\n    },\n    \"wordDelimitersDescription\": \"Voer een aangepaste Regex in om het aantal woorden te tellen of laat dit leeg voor de standaardwaarde.\",\n    \"wordDelimitersPlaceholder\": \"bijv. \\\\s.,;:!?\\\"«»()…\",\n    \"wordFrequencyAnalysis\": \"Woordfrequentieanalyse\",\n    \"wordFrequencyAnalysisDescription\": \"Tel hoe vaak elk woord in de tekst voorkomt\"\n  },\n  \"textReplacer\": {\n    \"description\": \"Vervang tekstpatronen door nieuwe inhoud.\",\n    \"findPatternInText\": \"Vind dit patroon in de tekst\",\n    \"findPatternUsingRegexp\": \"Vind een patroon met behulp van een RegExp\",\n    \"inputTitle\": \"Te vervangen tekst\",\n    \"newTextPlaceholder\": \"Nieuwe tekst\",\n    \"regexpDescription\": \"Voer de reguliere expressie in die u wilt vervangen.\",\n    \"replacePatternDescription\": \"Voer het patroon in dat u voor de vervanging wilt gebruiken.\",\n    \"replaceText\": \"Tekst vervangen\",\n    \"resultTitle\": \"Tekst met vervangingen\",\n    \"searchPatternDescription\": \"Voer het tekstpatroon in dat u wilt vervangen.\",\n    \"searchText\": \"Zoektekst\",\n    \"shortDescription\": \"Vervang snel tekst in uw inhoud\",\n    \"title\": \"Tekstvervanger\",\n    \"toolInfo\": {\n      \"description\": \"Vervang eenvoudig specifieke tekst in je content met deze eenvoudige, browsergebaseerde tool. Voer je tekst in, stel de tekst die je wilt vervangen en de vervangingswaarde in, en ontvang direct de bijgewerkte versie.\",\n      \"title\": \"Tekstvervanger\"\n    }\n  },\n  \"toMorse\": {\n    \"dashSymbolDescription\": \"Symbool dat overeenkomt met het streepje in Morsecode.\",\n    \"description\": \"Converteer tekst naar morsecode.\",\n    \"dotSymbolDescription\": \"Symbool dat overeenkomt met het punt in Morsecode.\",\n    \"longSignal\": \"Lang signaal\",\n    \"resultTitle\": \"Morsecode\",\n    \"shortDescription\": \"Snel tekst naar morse coderen\",\n    \"shortSignal\": \"Kort signaal\",\n    \"title\": \"String naar morse\"\n  },\n  \"truncate\": {\n    \"addTruncationIndicator\": \"Voeg afkappingsindicator toe\",\n    \"charactersPlaceholder\": \"Personages\",\n    \"description\": \"Kort de tekst in tot een bepaalde lengte.\",\n    \"indicatorDescription\": \"Tekens die aan het einde (of begin) van de tekst moeten worden toegevoegd. Let op: deze tellen mee voor de lengte.\",\n    \"inputTitle\": \"Invoertekst\",\n    \"leftSideDescription\": \"Verwijder tekens aan het begin van de tekst.\",\n    \"leftSideTruncation\": \"Linkerkant afkapping\",\n    \"lengthAndLines\": \"Lengte en lijnen\",\n    \"lineByLineDescription\": \"Knip elke regel afzonderlijk af.\",\n    \"lineByLineTruncating\": \"Regel-voor-regel afkappen\",\n    \"maxLengthDescription\": \"Aantal tekens dat in de tekst moet worden overgelaten.\",\n    \"numberPlaceholder\": \"Nummer\",\n    \"resultTitle\": \"Afgekorte tekst\",\n    \"rightSideDescription\": \"Verwijder tekens aan het einde van de tekst.\",\n    \"rightSideTruncation\": \"Afkapping aan de rechterkant\",\n    \"shortDescription\": \"Tekst afkappen tot een bepaalde lengte\",\n    \"suffixAndAffix\": \"Achtervoegsel en affix\",\n    \"title\": \"Tekst afbreken\",\n    \"toolInfo\": {\n      \"description\": \"Vul uw tekst in het invoerformulier aan de linkerkant in, dan krijgt u rechts automatisch afgekapte tekst te zien.\",\n      \"title\": \"Tekst afkappen\"\n    },\n    \"truncationSide\": \"Afkappingszijde\"\n  },\n  \"uppercase\": {\n    \"description\": \"Converteer tekst naar hoofdletters.\",\n    \"inputTitle\": \"Invoertekst\",\n    \"resultTitle\": \"Hoofdlettertekst\",\n    \"shortDescription\": \"Tekst naar hoofdletters converteren\",\n    \"title\": \"Omzetten naar hoofdletters\"\n  },\n  \"urlDecode\": {\n    \"inputTitle\": \"Invoerreeks (URL-escaped)\",\n    \"resultTitle\": \"Uitvoerreeks\",\n    \"toolInfo\": {\n      \"description\": \"Laad uw string en de URL-escaping wordt automatisch verwijderd.\",\n      \"longDescription\": \"Deze tool decodeert een eerder URL-gecodeerde string via URL. URL-decodering is de omgekeerde bewerking van URL-codering. Alle procentgecodeerde tekens worden gedecodeerd naar tekens die u kunt begrijpen. Enkele van de bekendste procentgecodeerde waarden zijn %20 voor een spatie, %3a voor een dubbele punt, %2f voor een slash en %3f voor een vraagteken. De twee cijfers na het procentteken zijn de hexadecimale tekencodewaarden.\",\n      \"shortDescription\": \"Snel URL-unescaping van een string uitvoeren.\",\n      \"title\": \"String URL-decoder\"\n    }\n  },\n  \"urlEncode\": {\n    \"encodingOption\": {\n      \"nonSpecialCharDescription\": \"Als u deze optie selecteert, worden alle tekens in de invoerreeks omgezet naar URL-codering (niet alleen speciale tekens).\",\n      \"nonSpecialCharPlaceholder\": \"Niet-speciale tekens coderen\",\n      \"title\": \"Coderingsopties\"\n    },\n    \"inputTitle\": \"Invoerreeks\",\n    \"resultTitle\": \"Url-escaped String\",\n    \"toolInfo\": {\n      \"description\": \"Laad uw string en deze wordt automatisch URL-escaped.\",\n      \"longDescription\": \"Deze tool URL-codeert een string. Speciale URL-tekens worden omgezet naar procenttekencodering. Deze codering wordt procentcodering genoemd, omdat de numerieke waarde van elk teken wordt omgezet naar een procentteken gevolgd door een tweecijferige hexadecimale waarde. De hexadecimale waarden worden bepaald op basis van de codepointwaarde van het teken. Een spatie wordt bijvoorbeeld geëscapete naar %20, een dubbele punt naar %3a en een slash naar %2f. Tekens die niet speciaal zijn, blijven ongewijzigd. Mocht u ook niet-speciale tekens naar procentcodering moeten converteren, dan hebben we ook een extra optie toegevoegd waarmee u dat kunt doen. Selecteer de optie 'encode-non-special-chars' om dit gedrag in te schakelen.\",\n      \"shortDescription\": \"Snelle URL-escaping van een string.\",\n      \"title\": \"String URL-encoder\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/time.json",
    "content": "{\n  \"checkLeapYears\": {\n    \"description\": \"Controleer of een jaar een schrikkeljaar is en ontvang informatie over schrikkeljaren.\",\n    \"exampleDescription\": \"Een van onze vriendinnen is geboren in een schrikkeljaar, op 29 februari, en daardoor is ze maar eens in de vier jaar jarig. Omdat we ons haar verjaardag nooit echt kunnen herinneren, gebruiken we ons programma om een herinneringslijst te maken voor de komende schrikkeljaren. Om een lijst met haar volgende verjaardagen te maken, laden we een reeks jaren van 2025 tot en met 2040 in de invoer en bekijken we de status van elk jaar. Als het programma aangeeft dat het een schrikkeljaar is, weten we dat we uitgenodigd worden voor een verjaardagsfeestje op 29 februari.\",\n    \"exampleTitle\": \"Vind verjaardagen op 29 februari\",\n    \"inputTitle\": \"Invoerjaar\",\n    \"resultTitle\": \"Resultaat van een schrikkeljaar\",\n    \"shortDescription\": \"Controleren of een jaar een schrikkeljaar is\",\n    \"title\": \"Controleer schrikkeljaren\",\n    \"toolInfo\": {\n      \"description\": \"Een schrikkeljaar is een jaar met een extra dag (29 februari) om het kalenderjaar gelijk te laten lopen met het astronomische jaar. Schrikkeljaren komen elke vier jaar voor, behalve jaren die deelbaar zijn door 100 maar niet door 400.\",\n      \"title\": \"Wat is een schrikkeljaar?\"\n    }\n  },\n  \"convertDaysToHours\": {\n    \"addHoursName\": \"Urennaam toevoegen\",\n    \"addHoursNameDescription\": \"Voeg de tekenreeks uren toe aan de uitvoerwaarden\",\n    \"description\": \"Converteer dagen naar uren met aanpasbare opties.\",\n    \"hoursName\": \"Uren Naam\",\n    \"shortDescription\": \"Dagen naar uren omrekenen\",\n    \"title\": \"Dagen naar uren converteren\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u dagen naar uren omrekenen. U kunt dagen invoeren als getallen of met eenheden, waarna de tool ze naar uren converteert. U kunt er ook voor kiezen om het achtervoegsel 'uren' aan de uitvoerwaarden toe te voegen.\",\n      \"title\": \"Dagen naar uren converteren\"\n    }\n  },\n  \"convertHoursToDays\": {\n    \"addDaysName\": \"Dagennaam toevoegen\",\n    \"addDaysNameDescription\": \"Voeg de tekenreeks dagen toe aan de uitvoerwaarden\",\n    \"daysName\": \"Dagen Naam\",\n    \"description\": \"Converteer uren naar dagen met aanpasbare opties.\",\n    \"shortDescription\": \"Uren naar dagen omrekenen\",\n    \"title\": \"Uren naar dagen omrekenen\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u uren naar dagen omrekenen. U kunt uren invoeren als getallen of met eenheden, waarna de tool ze naar dagen converteert. U kunt er ook voor kiezen om het achtervoegsel 'dagen' aan de uitvoerwaarden toe te voegen.\",\n      \"title\": \"Uren naar dagen omrekenen\"\n    }\n  },\n  \"convertSecondsToTime\": {\n    \"addPadding\": \"Opvulling toevoegen\",\n    \"addPaddingDescription\": \"Voeg nulvulling toe aan uren, minuten en seconden.\",\n    \"description\": \"Converteer seconden naar een leesbaar tijdsformaat (uren:minuten:seconden). Voer het aantal seconden in om de tijd in het juiste formaat te krijgen.\",\n    \"shortDescription\": \"Seconden naar tijdformaat converteren\",\n    \"timePadding\": \"Tijdvulling\",\n    \"title\": \"Seconden naar tijd converteren\",\n    \"toolInfo\": {\n      \"title\": \"Wat is een {{title}}?\"\n    }\n  },\n  \"convertTimeToSeconds\": {\n    \"description\": \"Converteer de geformatteerde tijd (UU:MM:SS) naar seconden.\",\n    \"inputTitle\": \"Invoertijd\",\n    \"resultTitle\": \"Seconden\",\n    \"shortDescription\": \"Tijdformaat naar seconden converteren\",\n    \"title\": \"Tijd naar seconden converteren\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kun je geformatteerde tijdreeksen (UU:MM:SS) omzetten naar seconden. Dit is handig voor het berekenen van tijdsduren en tijdsintervallen.\",\n      \"title\": \"Tijd naar seconden converteren\"\n    }\n  },\n  \"convertUnixToDate\": {\n    \"addUtcLabel\": \"Voeg 'UTC'-achtervoegsel toe\",\n    \"addUtcLabelDescription\": \"'UTC' weergeven na de geconverteerde datum (alleen voor UTC-modus)\",\n    \"description\": \"Converteer een Unix-tijdstempel naar een voor mensen leesbare datum.\",\n    \"outputOptions\": \"Uitvoeropties\",\n    \"shortDescription\": \"Unix-tijdstempel naar datum converteren\",\n    \"title\": \"Unix naar Date converteren\",\n    \"toolInfo\": {\n      \"description\": \"Deze tool converteert een Unix-tijdstempel (in seconden) naar een voor mensen leesbaar datumformaat (bijv. JJJJ-MM-DD UU:MM:SS). De tool ondersteunt zowel lokale als UTC-uitvoer, waardoor het handig is voor het snel interpreteren van tijdstempels uit logs, API's of systemen die Unix-tijd gebruiken.\",\n      \"title\": \"Unix naar Date converteren\"\n    },\n    \"useLocalTime\": \"Gebruik lokale tijd\",\n    \"useLocalTimeDescription\": \"Toon de geconverteerde datum in uw lokale tijdzone in plaats van UTC\",\n    \"withLabel\": \"Opties\"\n  },\n  \"crontabGuru\": {\n    \"description\": \"Genereer en begrijp cron-expressies. Maak cron-schema's voor geautomatiseerde taken en systeemtaken.\",\n    \"shortDescription\": \"Genereer en begrijp cron-expressies\",\n    \"title\": \"Crontab-goeroe\"\n  },\n  \"timeBetweenDates\": {\n    \"description\": \"Bereken het tijdsverschil tussen twee datums. Bereken de exacte duur in dagen, uren, minuten en seconden.\",\n    \"endDate\": \"Einddatum\",\n    \"endDateTime\": \"Einddatum en -tijd\",\n    \"endTime\": \"Eindtijd\",\n    \"endTimezone\": \"Eindtijdzone\",\n    \"shortDescription\": \"Bereken de tijd tussen twee data\",\n    \"startDate\": \"Startdatum\",\n    \"startDateTime\": \"Startdatum en -tijd\",\n    \"startTime\": \"Starttijd\",\n    \"startTimezone\": \"Starttijdzone\",\n    \"title\": \"Tijd tussen data\",\n    \"toolInfo\": {\n      \"description\": \"Bereken het exacte tijdsverschil tussen twee datums en tijden, met ondersteuning voor verschillende tijdzones. Deze tool biedt een gedetailleerde weergave van het tijdsverschil in verschillende eenheden (jaren, maanden, dagen, uren, minuten en seconden).\",\n      \"title\": \"Tijd tussen datums calculator\"\n    }\n  },\n  \"truncateClockTime\": {\n    \"description\": \"Kort de kloktijd in om seconden of minuten te verwijderen. Rond de tijd af naar het dichtstbijzijnde uur, minuut of aangepaste interval.\",\n    \"printDroppedComponents\": \"Afdrukken van verwijderde componenten\",\n    \"shortDescription\": \"De kloktijd afkappen tot de opgegeven precisie\",\n    \"timePadding\": \"Tijdvulling\",\n    \"title\": \"Kloktijd afkappen\",\n    \"toolInfo\": {\n      \"title\": \"Wat is een {{title}}?\"\n    },\n    \"truncateMinutesAndSeconds\": \"Minuten en seconden afkappen\",\n    \"truncateMinutesAndSecondsDescription\": \"Laat beide componenten – de minuten en seconden – van elke kloktijd weg.\",\n    \"truncateOnlySeconds\": \"Alleen seconden afkappen\",\n    \"truncateOnlySecondsDescription\": \"Verwijder het secondecomponent van elke kloktijd.\",\n    \"truncationSide\": \"Afkappingszijde\",\n    \"useZeroPadding\": \"Gebruik nulvulling\",\n    \"zeroPaddingDescription\": \"Zorg ervoor dat alle tijdcomponenten altijd twee cijfers breed zijn.\",\n    \"zeroPrintDescription\": \"Geef de verwijderde onderdelen weer als nulwaarden \\\"00\\\".\",\n    \"zeroPrintTruncatedParts\": \"Afgekapte onderdelen zonder afdrukken\"\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/translation.json",
    "content": "{\n  \"audio\": {\n    \"changeSpeed\": {\n      \"description\": \"Wijzig de afspeelsnelheid van audiobestanden. Versnel of vertraag audio met behoud van toonhoogte.\",\n      \"name\": \"Wijzig audiosnelheid\",\n      \"shortDescription\": \"De snelheid van audiobestanden wijzigen\"\n    },\n    \"extractAudio\": {\n      \"description\": \"Haal het audiospoor uit een videobestand en sla het op als een afzonderlijk audiobestand in het door u gekozen formaat (AAC, MP3, WAV).\",\n      \"name\": \"Audio extraheren\",\n      \"shortDescription\": \"Extraheer audio uit videobestanden (MP4, MOV, enz.) naar AAC, MP3 of WAV.\"\n    }\n  },\n  \"baseFileInput\": {\n    \"copyFailed\": \"Kopiëren mislukt: {{error}}\",\n    \"dropFileHere\": \"Laat je vallen {{type}} hier\",\n    \"fileCopied\": \"Bestand gekopieerd\",\n    \"selectFileDescription\": \"Klik hier om een te selecteren {{type}} Druk vanaf uw apparaat op Ctrl+V om een {{type}} vanaf uw klembord, of sleep en zet een bestand neer vanaf uw bureaublad\"\n  },\n  \"categories\": {\n    \"audio\": {\n      \"description\": \"Hulpmiddelen voor het werken met audio: audio uit video extraheren, de audiosnelheid aanpassen, meerdere audiobestanden samenvoegen en nog veel meer.\",\n      \"title\": \"Audio-hulpmiddelen\"\n    },\n    \"csv\": {\n      \"description\": \"Hulpmiddelen voor het werken met CSV-bestanden: converteer CSV naar verschillende formaten, manipuleer CSV-gegevens, valideer de CSV-structuur en verwerk CSV-bestanden efficiënt.\",\n      \"title\": \"CSV-hulpmiddelen\"\n    },\n    \"gif\": {\n      \"description\": \"Hulpmiddelen voor het werken met GIF-animaties: maak transparante GIF's, extraheer GIF-frames, voeg tekst toe aan GIF, snijd GIF's bij, draai ze, draai ze om en nog veel meer.\",\n      \"title\": \"GIF-hulpmiddelen\"\n    },\n    \"image-generic\": {\n      \"description\": \"Hulpmiddelen voor het werken met afbeeldingen: comprimeren, formaat wijzigen, bijsnijden, converteren naar JPG, roteren, achtergrond verwijderen en nog veel meer.\",\n      \"title\": \"Afbeeldingshulpmiddelen\"\n    },\n    \"json\": {\n      \"description\": \"Hulpmiddelen voor het werken met JSON-datastructuren: JSON-objecten mooier maken en verkleinen, JSON-arrays afvlakken, JSON-waarden stringen, gegevens analyseren en nog veel meer\",\n      \"title\": \"JSON-hulpmiddelen\"\n    },\n    \"list\": {\n      \"description\": \"Hulpmiddelen voor het werken met lijsten: lijsten sorteren, omkeren, willekeurig maken, unieke en dubbele listitems vinden, listitemscheidingstekens wijzigen en nog veel meer.\",\n      \"title\": \"Lijsthulpmiddelen\"\n    },\n    \"number\": {\n      \"description\": \"Hulpmiddelen voor het werken met getallen: genereer getallenreeksen, converteer getallen naar woorden en woorden naar getallen, sorteer, rond af, ontbind getallen en nog veel meer.\",\n      \"title\": \"Getallenhulpmiddelen\"\n    },\n    \"pdf\": {\n      \"description\": \"Hulpmiddelen voor het werken met PDF-bestanden: tekst uit PDF's extraheren, PDF's naar andere formaten converteren, PDF's bewerken en nog veel meer.\",\n      \"title\": \"PDF-hulpmiddelen\"\n    },\n    \"png\": {\n      \"description\": \"Hulpmiddelen voor het werken met PNG-afbeeldingen: converteer PNG's naar JPG's, maak transparante PNG's, wijzig PNG-kleuren, snijd PNG's bij, roteer ze, wijzig de grootte ervan en nog veel meer.\",\n      \"title\": \"PNG-hulpmiddelen\"\n    },\n    \"seeAll\": \"Bekijk alles {{title}}\",\n    \"string\": {\n      \"description\": \"Hulpmiddelen voor het werken met tekst: tekst naar afbeeldingen converteren, tekst zoeken en vervangen, tekst in fragmenten splitsen, tekstregels samenvoegen, tekst herhalen en nog veel meer.\",\n      \"title\": \"Teksthulpmiddelen\"\n    },\n    \"time\": {\n      \"description\": \"Hulpmiddelen voor het werken met tijd en datum: bereken tijdsverschillen, converteer tussen tijdzones, formatteer datums, genereer datumreeksen en nog veel meer.\",\n      \"title\": \"Tijdhulpmiddelen\"\n    },\n    \"try\": \"Poging {{title}}\",\n    \"video\": {\n      \"description\": \"Hulpmiddelen voor het werken met video's: frames uit video's halen, GIF's van video's maken, video's naar verschillende formaten converteren en nog veel meer.\",\n      \"title\": \"Videotools\"\n    },\n    \"xml\": {\n      \"description\": \"Hulpmiddelen voor het werken met XML-datastructuren - viewer, beautifier, validator en nog veel meer\",\n      \"title\": \"XML-hulpmiddelen\"\n    }\n  },\n  \"csv\": {\n    \"findIncompleteCsvRecords\": {\n      \"description\": \"Upload uw CSV-bestand via onderstaand formulier en deze tool controleert automatisch of er geen waarden in de rijen of kolommen ontbreken. In de toolopties kunt u de opmaak van het invoerbestand aanpassen (het scheidingsteken, de aanhalingstekens en het commentaarteken opgeven). Daarnaast kunt u de controle op lege waarden inschakelen, lege regels overslaan en een limiet instellen voor het aantal foutmeldingen in de uitvoer.\",\n      \"name\": \"Onvolledige CSV-records vinden\",\n      \"shortDescription\": \"Vind snel rijen en kolommen in CSV waar waarden ontbreken.\"\n    }\n  },\n  \"hero\": {\n    \"brand\": \"OmniTools\",\n    \"description\": \"Verhoog je productiviteit met OmniTools, de ultieme toolkit om snel aan de slag te gaan! Krijg toegang tot duizenden gebruiksvriendelijke tools voor het bewerken van afbeeldingen, tekst, lijsten en gegevens, allemaal rechtstreeks vanuit je browser.\",\n    \"examples\": {\n      \"calculateNumberSum\": \"Bereken de getallensom\",\n      \"changeGifSpeed\": \"GIF-snelheid wijzigen\",\n      \"compressPng\": \"PNG comprimeren\",\n      \"createTransparentImage\": \"Een transparante afbeelding maken\",\n      \"prettifyJson\": \"JSON verfraaien\",\n      \"sortList\": \"Een lijst sorteren\",\n      \"splitPdf\": \"PDF splitsen\",\n      \"splitText\": \"Een tekst splitsen\",\n      \"trimVideo\": \"Video bijsnijden\"\n    },\n    \"searchPlaceholder\": \"Zoek in alle tools\",\n    \"title\": \"Krijg dingen snel gedaan met\"\n  },\n  \"inputFooter\": {\n    \"clear\": \"Duidelijk\",\n    \"copyToClipboard\": \"Kopiëren naar klembord\",\n    \"importFromFile\": \"Importeren uit bestand\"\n  },\n  \"list\": {\n    \"group\": {\n      \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het groeperen van lijstitems. Voer uw lijst in en specificeer groeperingscriteria om items in logische groepen te ordenen. Perfect voor het categoriseren van gegevens, het organiseren van informatie of het maken van gestructureerde lijsten. Ondersteunt aangepaste scheidingstekens en diverse groeperingsopties.\",\n      \"name\": \"Groep\",\n      \"shortDescription\": \"Groepeer lijstitems op basis van gemeenschappelijke eigenschappen\"\n    },\n    \"reverse\": {\n      \"description\": \"Dit is een supereenvoudige browsergebaseerde applicatie die alle items in de lijst in spiegelbeeld afdrukt. De invoeritems kunnen worden gescheiden door een willekeurig symbool en u kunt ook de scheidingstekens van de omgekeerde items in de lijst wijzigen.\",\n      \"name\": \"Achteruit\",\n      \"shortDescription\": \"Snel een lijst omkeren\"\n    },\n    \"sort\": {\n      \"description\": \"Dit is een supereenvoudige browsergebaseerde applicatie die items in een lijst sorteert en in oplopende of aflopende volgorde rangschikt. U kunt de items alfabetisch, numeriek of op lengte sorteren. U kunt ook dubbele en lege items verwijderen, en afzonderlijke items met witruimte eromheen verwijderen. U kunt elk scheidingsteken gebruiken om de items in de invoerlijst te scheiden, of u kunt een reguliere expressie gebruiken om ze te scheiden. Daarnaast kunt u een nieuw scheidingsteken maken voor de gesorteerde uitvoerlijst.\",\n      \"name\": \"Soort\",\n      \"shortDescription\": \"Snel een lijst sorteren\"\n    }\n  },\n  \"navbar\": {\n    \"buyMeACoffee\": \"Koop me een koffie\",\n    \"hireMe\": \"Neem mij aan\",\n    \"home\": \"Thuis\",\n    \"tools\": \"Hulpmiddelen\"\n  },\n  \"number\": {\n    \"generate\": {\n      \"description\": \"Bereken snel een lijst met gehele getallen in uw browser. Om uw lijst te verkrijgen, specificeert u het eerste gehele getal, wijzigt u de waarde en het totale aantal in de onderstaande opties, en deze tool genereert dat aantal gehele getallen.\",\n      \"name\": \"Genereer getallen\",\n      \"shortDescription\": \"Bereken snel een lijst met gehele getallen in uw browser\"\n    },\n    \"sum\": {\n      \"description\": \"Dit is een supereenvoudige browserapplicatie die getallen optelt. De ingevoerde getallen kunnen worden gescheiden door een willekeurig symbool en je kunt ook het scheidingsteken van de opgetelde getallen wijzigen.\",\n      \"name\": \"Som getallen\",\n      \"shortDescription\": \"Snel een lijst met getallen optellen\"\n    }\n  },\n  \"numericInputWithUnit\": {\n    \"unit\": \"Eenheid\"\n  },\n  \"pdf\": {\n    \"compressPdf\": {\n      \"description\": \"Verklein de PDF-bestandsgrootte met behoud van kwaliteit met Ghostscript\",\n      \"name\": \"PDF comprimeren\",\n      \"shortDescription\": \"Comprimeer PDF-bestanden veilig in uw browser\"\n    },\n    \"mergePdf\": {\n      \"description\": \"Combineer meerdere PDF-bestanden tot één document.\",\n      \"name\": \"PDF samenvoegen\",\n      \"shortDescription\": \"Meerdere PDF-bestanden samenvoegen tot één document\"\n    },\n    \"pdfToEpub\": {\n      \"description\": \"Transformeer PDF-documenten naar EPUB-bestanden voor betere compatibiliteit met e-readers.\",\n      \"name\": \"PDF naar EPUB\",\n      \"shortDescription\": \"PDF-bestanden converteren naar EPUB-formaat\"\n    },\n    \"protectPdf\": {\n      \"description\": \"Voeg wachtwoordbeveiliging toe aan uw PDF-bestanden veilig in uw browser\",\n      \"name\": \"PDF beschermen\",\n      \"shortDescription\": \"PDF-bestanden veilig met een wachtwoord beveiligen\"\n    },\n    \"splitPdf\": {\n      \"description\": \"Specifieke pagina's uit een PDF-bestand extraheren met behulp van paginanummers of bereiken (bijv. 1, 5-8)\",\n      \"name\": \"PDF splitsen\",\n      \"shortDescription\": \"Specifieke pagina's uit een PDF-bestand extraheren\"\n    }\n  },\n  \"resultFooter\": {\n    \"copy\": \"Kopiëren naar klembord\",\n    \"download\": \"Download\"\n  },\n  \"string\": {\n    \"createPalindrome\": {\n      \"description\": \"'s Werelds eenvoudigste browsergebaseerde tool voor het maken van palindromen van elke tekst. Voer tekst in en transformeer deze direct in een palindroom die zowel van voor naar achter als van achter naar voren hetzelfde leest. Perfect voor woordspelletjes, het creëren van symmetrische tekstpatronen of het verkennen van taalkundige curiosa.\",\n      \"name\": \"Palindroom maken\",\n      \"shortDescription\": \"Maak een tekst die van voor naar achter en van achter naar voren hetzelfde leest\"\n    },\n    \"palindrome\": {\n      \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma om te controleren of tekst een palindroom is. Controleer direct of uw tekst van voor naar achteren hetzelfde leest. Perfect voor woordpuzzels, taalkundige analyses of het valideren van symmetrische tekstpatronen. Ondersteunt diverse scheidingstekens en palindroomdetectie van meerdere woorden.\",\n      \"name\": \"Palindroom\",\n      \"shortDescription\": \"Controleren of de tekst van voor naar achter hetzelfde leest\"\n    },\n    \"repeat\": {\n      \"description\": \"Met deze tool kunt u een bepaalde tekst meerdere keren herhalen met een optionele scheidingsteken.\",\n      \"name\": \"Herhaal tekst\",\n      \"shortDescription\": \"Herhaal de tekst meerdere keren\"\n    },\n    \"reverse\": {\n      \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het omkeren van tekst. Voer tekst in en laat deze direct omkeren, teken voor teken. Perfect voor het maken van spiegeltekst, het analyseren van palindromen of het experimenteren met tekstpatronen. Spaties en speciale tekens blijven behouden tijdens het omkeren.\",\n      \"name\": \"Achteruit\",\n      \"shortDescription\": \"Elke tekst teken voor teken omdraaien\"\n    },\n    \"toMorse\": {\n      \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het converteren van tekst naar morsecode. Laad uw tekst in het invoerveld aan de linkerkant en u krijgt direct morsecode in het uitvoergebied. Krachtig, gratis en snel. Laad tekst – ontvang morsecode.\",\n      \"name\": \"String naar morse\",\n      \"shortDescription\": \"Snel tekst naar morse coderen\"\n    },\n    \"uppercase\": {\n      \"description\": \"'s Werelds eenvoudigste browsergebaseerde hulpprogramma voor het omzetten van tekst naar hoofdletters. Voer uw tekst in en deze wordt automatisch omgezet naar alleen hoofdletters. Perfect voor het maken van koppen, het benadrukken van tekst of het standaardiseren van tekstopmaak. Ondersteunt diverse tekstformaten en behoudt speciale tekens.\",\n      \"name\": \"Hoofdletters\",\n      \"shortDescription\": \"Tekst naar hoofdletters converteren\"\n    }\n  },\n  \"toolExamples\": {\n    \"subtitle\": \"Klik om te proberen!\",\n    \"title\": \"{{title}} Voorbeelden\"\n  },\n  \"toolFileResult\": {\n    \"copied\": \"Bestand gekopieerd\",\n    \"copyFailed\": \"Kopiëren mislukt: {{error}}\",\n    \"loading\": \"Bezig met laden... Dit kan even duren.\",\n    \"result\": \"Resultaat\"\n  },\n  \"toolHeader\": {\n    \"seeExamples\": \"Zie voorbeelden\"\n  },\n  \"toolLayout\": {\n    \"allToolsTitle\": \"Alle {{type}}\"\n  },\n  \"toolMultiFileResult\": {\n    \"copied\": \"Bestand gekopieerd\",\n    \"copyFailed\": \"Kopiëren mislukt: {{error}}\",\n    \"loading\": \"Bezig met laden... Dit kan even duren.\",\n    \"result\": \"Resultaat\"\n  },\n  \"toolMultipleAudioInput\": {\n    \"inputTitle\": \"Invoer {{type}}\",\n    \"noFilesSelected\": \"Geen bestanden geselecteerd\"\n  },\n  \"toolMultiplePdfInput\": {\n    \"inputTitle\": \"Invoer {{type}}\",\n    \"noFilesSelected\": \"Geen bestanden geselecteerd\"\n  },\n  \"toolOptions\": {\n    \"title\": \"Gereedschapsopties\"\n  },\n  \"toolTextInput\": {\n    \"copied\": \"Tekst gekopieerd\",\n    \"copyFailed\": \"Kopiëren mislukt: {{error}}\",\n    \"input\": \"Invoertekst\",\n    \"placeholder\": \"Voer hier uw tekst in...\"\n  },\n  \"toolTextResult\": {\n    \"copied\": \"Tekst gekopieerd\",\n    \"copyFailed\": \"Kopiëren mislukt: {{error}}\",\n    \"loading\": \"Bezig met laden... Dit kan even duren.\",\n    \"result\": \"Resultaat\"\n  },\n  \"userTypes\": {\n    \"developers\": \"Ontwikkelaars\",\n    \"generalUsers\": \"Algemene gebruikers\"\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/video.json",
    "content": "{\n  \"changeSpeed\": {\n    \"defaultMultiplier\": \"Standaardvermenigvuldiger: 2 betekent 2x sneller\",\n    \"description\": \"Wijzig de afspeelsnelheid van videobestanden. Versnel of vertraag video's met behoud van audiosynchronisatie. Ondersteunt diverse snelheidsvermenigvuldigers en gangbare videoformaten.\",\n    \"inputTitle\": \"Invoervideo\",\n    \"newVideoSpeed\": \"Nieuwe videosnelheid\",\n    \"resultTitle\": \"Bewerkte video\",\n    \"settingSpeed\": \"Snelheid instellen\",\n    \"shortDescription\": \"Wijzig de afspeelsnelheid van video\",\n    \"title\": \"Videosnelheid wijzigen\",\n    \"toolInfo\": {\n      \"title\": \"Wat is een {{title}}?\"\n    }\n  },\n  \"compress\": {\n    \"default\": \"Standaard\",\n    \"description\": \"Comprimeer video's door ze te schalen naar verschillende resoluties, zoals 240p, 480p, 720p, enz. Deze tool helpt de bestandsgrootte te verkleinen met behoud van een acceptabele kwaliteit. Ondersteunt gangbare videoformaten zoals MP4, WebM en OGG.\",\n    \"inputTitle\": \"Invoervideo\",\n    \"loadingText\": \"Video comprimeren...\",\n    \"lossless\": \"Verliesloos\",\n    \"quality\": \"Kwaliteit (CRF)\",\n    \"resolution\": \"Oplossing\",\n    \"resultTitle\": \"Gecomprimeerde video\",\n    \"shortDescription\": \"Comprimeer video's door te schalen naar verschillende resoluties\",\n    \"title\": \"Video comprimeren\",\n    \"worst\": \"Slechtst\"\n  },\n  \"cropVideo\": {\n    \"cropCoordinates\": \"Gewascoördinaten\",\n    \"croppingVideo\": \"Video bijsnijden\",\n    \"description\": \"Snijd de video bij om ongewenste delen te verwijderen.\",\n    \"errorBeyondHeight\": \"Het gewasgebied strekt zich uit voorbij de videohoogte ({{height}}px)\",\n    \"errorBeyondWidth\": \"Het bijsnijdgebied is groter dan de videobreedte ({{width}}px)\",\n    \"errorCroppingVideo\": \"Fout bij het bijsnijden van de video. Controleer de parameters en het videobestand.\",\n    \"errorLoadingDimensions\": \"Het laden van de video-afmetingen is mislukt\",\n    \"errorNonNegativeCoordinates\": \"X- en Y-coördinaten moeten niet-negatief zijn\",\n    \"errorPositiveDimensions\": \"Breedte en hoogte moeten positief zijn\",\n    \"height\": \"Hoogte\",\n    \"inputTitle\": \"Invoervideo\",\n    \"loadVideoForDimensions\": \"Laad een video om de afmetingen te zien\",\n    \"longDescription\": \"Met deze tool kun je videobestanden bijsnijden om ongewenste delen te verwijderen of om de focus te leggen op specifieke delen van de video. Handig voor het verwijderen van zwarte balken, het aanpassen van beeldverhoudingen of het focussen op belangrijke content. Ondersteunt diverse videoformaten, waaronder MP4, MOV en AVI.\",\n    \"resultTitle\": \"Bijgesneden video\",\n    \"shortDescription\": \"Video bijsnijden om ongewenste gebieden te verwijderen\",\n    \"title\": \"Bijsnijden Video\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u videobestanden bijsnijden om ongewenste delen te verwijderen. U kunt het bijsnijdgebied specificeren door de X- en Y-coördinaten en de breedte- en hoogte-afmetingen in te stellen.\",\n      \"title\": \"Bijsnijden Video\"\n    },\n    \"videoDimensions\": \"Video-afmetingen: {{width}} × {{height}} pixels\",\n    \"videoInformation\": \"Video-informatie\",\n    \"width\": \"Breedte\",\n    \"xCoordinate\": \"X (links)\",\n    \"yCoordinate\": \"Y (boven)\"\n  },\n  \"flip\": {\n    \"description\": \"Draai videobestanden horizontaal of verticaal. Spiegel video's voor speciale effecten of om problemen met de oriëntatie te corrigeren.\",\n    \"flippingVideo\": \"Flipping Video\",\n    \"horizontalLabel\": \"Horizontaal (Spiegel)\",\n    \"inputTitle\": \"Invoervideo\",\n    \"orientation\": \"Oriëntatie\",\n    \"resultTitle\": \"Omgedraaide video\",\n    \"shortDescription\": \"Video horizontaal of verticaal spiegelen\",\n    \"title\": \"Flip Video\",\n    \"verticalLabel\": \"Verticaal (op zijn kop)\"\n  },\n  \"gif\": {\n    \"changeSpeed\": {\n      \"description\": \"Wijzig de afspeelsnelheid van GIF-animaties. Versnel of vertraag GIF's met behoud van vloeiende animaties.\",\n      \"shortDescription\": \"Verander de GIF-animatiesnelheid\",\n      \"title\": \"GIF-snelheid wijzigen\"\n    }\n  },\n  \"loop\": {\n    \"description\": \"Maak een herhalende video door de originele video meerdere keren te herhalen.\",\n    \"inputTitle\": \"Invoervideo\",\n    \"loopingVideo\": \"Video herhalen\",\n    \"loops\": \"Lussen\",\n    \"numberOfLoops\": \"Aantal lussen\",\n    \"resultTitle\": \"Geloopte video\",\n    \"shortDescription\": \"Maak herhalende videobestanden\",\n    \"title\": \"Loop-video\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kun je een herhalende video maken door de originele video meerdere keren te herhalen. Je kunt zelf aangeven hoe vaak de video moet worden herhaald.\",\n      \"title\": \"Wat is een {{title}}?\"\n    }\n  },\n  \"mergeVideo\": {\n    \"description\": \"Combineer meerdere videobestanden tot één doorlopende video.\",\n    \"longDescription\": \"Met deze tool kun je meerdere videobestanden samenvoegen of toevoegen tot één doorlopende video. Upload je videobestanden, rangschik ze in de gewenste volgorde en voeg ze samen tot één bestand om ze eenvoudig te delen of te bewerken.\",\n    \"shortDescription\": \"Eenvoudig video's toevoegen en samenvoegen.\",\n    \"title\": \"Video's samenvoegen\"\n  },\n  \"rotate\": {\n    \"180Degrees\": \"180° (op zijn kop)\",\n    \"270Degrees\": \"270° (90° tegen de klok in)\",\n    \"90Degrees\": \"90° met de klok mee\",\n    \"description\": \"Roteer videobestanden 90, 180 of 270 graden. Corrigeer de video-oriëntatie of creëer speciale effecten met nauwkeurige rotatiecontrole.\",\n    \"inputTitle\": \"Invoervideo\",\n    \"resultTitle\": \"Gedraaide video\",\n    \"rotatingVideo\": \"Roterende video\",\n    \"rotation\": \"Rotatie\",\n    \"shortDescription\": \"Video met opgegeven graden roteren\",\n    \"title\": \"Video roteren\"\n  },\n  \"trim\": {\n    \"description\": \"Trim videobestanden door begin- en eindtijden op te geven. Verwijder ongewenste delen aan het begin of einde van video's.\",\n    \"endTime\": \"Eindtijd\",\n    \"inputTitle\": \"Invoervideo\",\n    \"resultTitle\": \"Bijgesneden video\",\n    \"shortDescription\": \"Video bijsnijden door ongewenste delen te verwijderen\",\n    \"startTime\": \"Starttijd\",\n    \"timestamps\": \"Tijdstempels\",\n    \"title\": \"Video bijsnijden\"\n  },\n  \"videoToGif\": {\n    \"description\": \"Converteer videobestanden naar geanimeerde GIF-indeling. Extraheer specifieke tijdsperioden en maak deelbare geanimeerde afbeeldingen.\",\n    \"shortDescription\": \"Converteer video naar geanimeerde GIF\",\n    \"title\": \"Video naar GIF\"\n  }\n}\n"
  },
  {
    "path": "public/locales/nl/xml.json",
    "content": "{\n  \"xmlBeautifier\": {\n    \"description\": \"Formatteer XML met de juiste inspringing en spatie.\",\n    \"indentation\": \"Inspringing\",\n    \"inputTitle\": \"Invoer XML\",\n    \"resultTitle\": \"Verfraaide XML\",\n    \"shortDescription\": \"XML-code opmaken en verfraaien\",\n    \"title\": \"XML-verfraaier\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u XML-gegevens opmaken met de juiste inspringing en spaties, waardoor ze beter leesbaar worden en u er gemakkelijker mee kunt werken.\",\n      \"title\": \"XML-verfraaier\"\n    },\n    \"useSpaces\": \"Gebruik spaties\",\n    \"useSpacesDescription\": \"Uitvoer inspringen met spaties\",\n    \"useTabs\": \"Tabbladen gebruiken\",\n    \"useTabsDescription\": \"Uitvoer inspringen met tabs.\"\n  },\n  \"xmlValidator\": {\n    \"description\": \"Valideer XML-syntaxis en -structuur.\",\n    \"placeholder\": \"Plak of importeer hier XML...\",\n    \"shortDescription\": \"XML-code valideren op fouten\",\n    \"title\": \"XML-validator\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u de XML-syntaxis en -structuur valideren. De tool controleert of de XML correct is opgemaakt en geeft gedetailleerde foutmeldingen bij gevonden problemen.\",\n      \"title\": \"XML-validator\"\n    }\n  },\n  \"xmlViewer\": {\n    \"description\": \"Bekijk en verken de XML-structuur in een boomstructuur.\",\n    \"inputTitle\": \"Invoer XML\",\n    \"resultTitle\": \"XML-boomweergave\",\n    \"title\": \"XML-viewer\",\n    \"toolInfo\": {\n      \"description\": \"Met deze tool kunt u XML-gegevens in een hiërarchische boomstructuur bekijken, waardoor u de structuur van XML-documenten eenvoudiger kunt verkennen en begrijpen.\",\n      \"title\": \"XML-viewer\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/audio.json",
    "content": "{\n  \"changeSpeed\": {\n    \"description\": \"Altere a velocidade de reprodução dos arquivos de áudio. Acelere ou desacelere o áudio, mantendo o tom.\",\n    \"inputTitle\": \"Áudio de entrada\",\n    \"newAudioSpeed\": \"Nova velocidade de áudio\",\n    \"outputFormat\": \"Formato de saída\",\n    \"resultTitle\": \"Áudio editado\",\n    \"settingSpeed\": \"Definindo a velocidade\",\n    \"shortDescription\": \"Alterar a velocidade dos arquivos de áudio\",\n    \"speedDescription\": \"Multiplicador padrão: 2 significa 2x mais rápido\",\n    \"title\": \"Alterar velocidade do áudio\",\n    \"toolInfo\": {\n      \"title\": \"O que é {{title}}?\"\n    }\n  },\n  \"extractAudio\": {\n    \"description\": \"Extraia faixas de áudio de arquivos de vídeo.\",\n    \"extractingAudio\": \"Extraindo áudio\",\n    \"inputTitle\": \"Entrada de vídeo\",\n    \"outputFormat\": \"Formato de saída\",\n    \"outputFormatDescription\": \"Selecione o formato para o áudio ser extraído.\",\n    \"resultTitle\": \"Áudio extraído\",\n    \"shortDescription\": \"Extraia áudio de arquivos de vídeo (MP4, MOV, etc.) para AAC, MP3 ou WAV.\",\n    \"title\": \"Extrair áudio de vídeo\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite extrair a faixa de áudio de arquivos de vídeo. Você pode escolher entre diferentes formatos de áudio, incluindo AAC, MP3 e WAV.\",\n      \"title\": \"O que é {{title}}?\"\n    }\n  },\n  \"mergeAudio\": {\n    \"description\": \"Combine vários arquivos de áudio em um único arquivo de áudio concatenando-os em sequência.\",\n    \"inputTitle\": \"Arquivos de áudio de entrada\",\n    \"longDescription\": \"Esta ferramenta permite mesclar vários arquivos de áudio em um único arquivo, concatenando-os na ordem em que foram enviados. Perfeito para combinar segmentos de podcast, faixas de música ou qualquer arquivo de áudio que precise ser unido. Suporta vários formatos de áudio, incluindo MP3, AAC e WAV.\",\n    \"mergingAudio\": \"Mesclando áudio\",\n    \"outputFormat\": \"Formato de saída\",\n    \"resultTitle\": \"Áudio mesclado\",\n    \"shortDescription\": \"Mescle vários arquivos de áudio em um (MP3, AAC, WAV).\",\n    \"title\": \"Mesclar áudio\",\n    \"toolInfo\": {\n      \"title\": \"O que é {{title}}?\"\n    }\n  },\n  \"trim\": {\n    \"description\": \"Corte e apare arquivos de áudio para extrair segmentos específicos, especificando horários de início e término.\",\n    \"endTime\": \"Fim dos tempos\",\n    \"endTimeDescription\": \"Hora final no formato HH:MM:SS (por exemplo, 00:01:30)\",\n    \"inputTitle\": \"Áudio de entrada\",\n    \"longDescription\": \"Esta ferramenta permite cortar arquivos de áudio especificando os tempos de início e término. Você pode extrair segmentos específicos de arquivos de áudio mais longos, remover partes indesejadas ou criar clipes mais curtos. Suporta vários formatos de áudio, incluindo MP3, AAC e WAV. Perfeito para edição de podcasts, produção musical ou qualquer necessidade de edição de áudio.\",\n    \"outputFormat\": \"Formato de saída\",\n    \"resultTitle\": \"Áudio aparado\",\n    \"shortDescription\": \"Corte arquivos de áudio para extrair segmentos de tempo específicos (MP3, AAC, WAV).\",\n    \"startTime\": \"Hora de início\",\n    \"startTimeDescription\": \"Hora de início no formato HH:MM:SS (por exemplo, 00:00:30)\",\n    \"timeSettings\": \"Configurações de tempo\",\n    \"title\": \"Aparar áudio\",\n    \"toolInfo\": {\n      \"title\": \"O que é {{title}}?\"\n    },\n    \"trimmingAudio\": \"Cortando áudio\"\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/converters.json",
    "content": "{\n  \"audioConverter\": {\n    \"title\": \"Conversor de Áudio\",\n    \"description\": \"Converta arquivos de áudio entre diferentes formatos.\",\n    \"shortDescription\": \"Converta arquivos de áudio para vários formatos.\",\n    \"longDescription\": \"Esta ferramenta permite que você converta arquivos de áudio de um formato para outro, suportando uma ampla gama de formatos de áudio para conversão perfeita.\",\n    \"outputFormat\": \"Formato de Saída\",\n    \"outputFormatDescription\": \"Selecione o formato de áudio de saída desejado\",\n    \"inputTitle\": \"Entrada de Áudio\",\n    \"outputTitle\": \"Áudio Convertido\"\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/csv.json",
    "content": "{\n  \"changeCsvSeparator\": {\n    \"description\": \"Altere o delimitador/separador em arquivos CSV. Converta entre diferentes formatos CSV, como vírgula, ponto e vírgula, tabulação ou separadores personalizados.\",\n    \"shortDescription\": \"Alterar delimitador de arquivo CSV\",\n    \"title\": \"Alterar separador CSV\"\n  },\n  \"csvRowsToColumns\": {\n    \"description\": \"Esta ferramenta converte linhas de um arquivo CSV (Valores Separados por Vírgula) em colunas. Ela extrai as linhas horizontais do arquivo CSV de entrada, uma a uma, gira-as 90 graus e as gera como colunas verticais, uma após a outra, separadas por vírgulas.', longDescription: 'Esta ferramenta converte linhas de um arquivo CSV (Valores Separados por Vírgula) em colunas. Por exemplo, se os dados CSV de entrada tiverem 6 linhas, a saída terá 6 colunas e os elementos das linhas serão organizados de cima para baixo. Em um CSV bem formado, o número de valores em cada linha é o mesmo. No entanto, nos casos em que as linhas não possuem campos, o programa pode corrigi-los e você pode escolher entre as opções disponíveis: preencher os dados ausentes com elementos vazios ou substituir os dados ausentes por elementos personalizados, como \\\"ausente\\\", \\\"?\\\" ou \\\"x\\\". Durante o processo de conversão, a ferramenta também limpa o arquivo CSV de informações desnecessárias, como linhas vazias (linhas sem informações visíveis) e comentários. Para ajudar a ferramenta a identificar corretamente os comentários, nas opções, você pode especificar o símbolo no início de uma linha que inicia um comentário. Este símbolo normalmente é um hash \\\"#\\\" ou uma barra dupla \\\"//\\\". Csv-abulous!.\",\n    \"longDescription\": \"Esta ferramenta converte linhas de um arquivo CSV (Valores Separados por Vírgula) em colunas. Por exemplo, se os dados CSV de entrada tiverem 6 linhas, a saída terá 6 colunas e os elementos das linhas serão organizados de cima para baixo. Em um CSV bem formado, o número de valores em cada linha é o mesmo. No entanto, nos casos em que as linhas não possuem campos, o programa pode corrigi-los e você pode escolher entre as opções disponíveis: preencher os dados ausentes com elementos vazios ou substituir os dados ausentes por elementos personalizados, como\",\n    \"shortDescription\": \"Converter linhas CSV em colunas.\",\n    \"title\": \"Converter linhas CSV em colunas\"\n  },\n  \"csvToJson\": {\n    \"columnSeparator\": \"Separador de coluna (por exemplo, , ; \\\\t)\",\n    \"commentSymbol\": \"Símbolo de comentário (por exemplo, #)\",\n    \"conversionOptions\": \"Opções de conversão\",\n    \"description\": \"Converta arquivos CSV para o formato JSON com opções personalizáveis para delimitadores, aspas e formatação de saída. Suporte para cabeçalhos, comentários e conversão dinâmica de tipos.\",\n    \"dynamicTypes\": \"Tipos dinâmicos\",\n    \"dynamicTypesDescription\": \"Converta números e booleanos automaticamente\",\n    \"error\": \"Erro\",\n    \"errorParsing\": \"Erro ao analisar CSV: {{error}}\",\n    \"fieldQuote\": \"Citação de campo (por exemplo, \\\")\",\n    \"inputCsvFormat\": \"Formato CSV de entrada\",\n    \"inputTitle\": \"Entrada CSV\",\n    \"invalidCsvFormat\": \"Formato CSV inválido\",\n    \"resultTitle\": \"Saída JSON\",\n    \"shortDescription\": \"Converta dados CSV para o formato JSON.\",\n    \"skipEmptyLines\": \"Pular linhas vazias\",\n    \"skipEmptyLinesDescription\": \"Ignorar linhas vazias no CSV de entrada\",\n    \"title\": \"Converter CSV para JSON\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta transforma arquivos CSV (Valores Separados por Vírgula) em estruturas de dados JavaScript Object Notation (JSON). Ela suporta diversos formatos CSV com delimitadores, aspas e símbolos de comentário personalizáveis. O conversor pode tratar a primeira linha como cabeçalho, pular linhas vazias e detectar automaticamente tipos de dados como números e booleanos. O JSON resultante pode ser usado para migração de dados, backups ou como entrada para outros aplicativos.\",\n      \"title\": \"O que é um conversor de CSV para JSON?\"\n    },\n    \"useHeaders\": \"Usar Cabeçalhos\",\n    \"useHeadersDescription\": \"Tratar a primeira linha como cabeçalhos de coluna\"\n  },\n  \"csvToTsv\": {\n    \"description\": \"Carregue seu arquivo CSV no formulário abaixo e ele será convertido automaticamente para um arquivo TSV. Nas opções da ferramenta, você pode personalizar o formato CSV de entrada – especificar o delimitador de campo, o caractere de aspas e o símbolo de comentário, além de pular linhas CSV vazias e escolher se deseja preservar os cabeçalhos das colunas CSV.\",\n    \"longDescription\": \"Esta ferramenta transforma dados de Valores Separados por Vírgula (CSV) em dados de Valores Separados por Tabulação (TSV). CSV e TSV são formatos de arquivo populares para armazenar dados tabulares, mas usam delimitadores diferentes para separar valores – CSV usa vírgulas (\",\n    \"shortDescription\": \"Converter dados CSV para o formato TSV.\",\n    \"title\": \"Converter CSV para TSV\"\n  },\n  \"csvToXml\": {\n    \"description\": \"Converta arquivos CSV para o formato XML com opções personalizáveis.\",\n    \"shortDescription\": \"Converta dados CSV para o formato XML.\",\n    \"title\": \"Converter CSV para XML\"\n  },\n  \"csvToYaml\": {\n    \"description\": \"Basta enviar seu arquivo CSV no formulário abaixo e ele será convertido automaticamente para um arquivo YAML. Nas opções da ferramenta, você pode especificar o caractere delimitador de campo, o caractere de aspas de campo e o caractere de comentário para adaptar a ferramenta a formatos CSV personalizados. Além disso, você pode selecionar o formato YAML de saída: um que preserva os cabeçalhos CSV ou um que exclui os cabeçalhos CSV.\",\n    \"longDescription\": \"Esta ferramenta transforma dados CSV (Valores Separados por Vírgula) em dados YAML (Yet Another Markup Language). CSV é um formato tabular simples usado para representar tipos de dados matriciais, compostos por linhas e colunas. YAML, por outro lado, é um formato mais avançado (na verdade, um superconjunto de JSON), que cria dados mais legíveis para serialização e oferece suporte a listas, dicionários e objetos aninhados. Este programa oferece suporte a vários formatos CSV de entrada – os dados de entrada podem ser separados por vírgula (padrão), ponto e vírgula, barra vertical ou usar outro delimitador completamente diferente. Você pode especificar o delimitador exato que seus dados usam nas opções. Da mesma forma, nas opções, você pode especificar o caractere de aspas usado para quebrar campos CSV (por padrão, um símbolo de aspas duplas). Você também pode pular linhas que começam com comentários, especificando os símbolos de comentário nas opções. Isso permite que você mantenha seus dados limpos, pulando linhas desnecessárias. Há duas maneiras de converter CSV para YAML. O primeiro método converte cada linha CSV em uma lista YAML. O segundo método extrai cabeçalhos da primeira linha CSV e cria objetos YAML com chaves baseadas nesses cabeçalhos. Você também pode personalizar o formato YAML de saída especificando o número de espaços para recuar as estruturas YAML. Se precisar realizar a conversão reversa, ou seja, transformar YAML em CSV, você pode usar nossa ferramenta Converter YAML para CSV. CSV-abulous!\",\n    \"shortDescription\": \"Converta rapidamente um arquivo CSV em um arquivo YAML.\",\n    \"title\": \"Converter CSV para YAML\"\n  },\n  \"findIncompleteCsvRecords\": {\n    \"checkingOptions\": \"Opções de verificação\",\n    \"commentCharacterDescription\": \"Insira o caractere que indica o início de uma linha de comentário. Linhas que começam com este símbolo serão ignoradas.\",\n    \"csvInputOptions\": \"Opções de entrada CSV\",\n    \"csvSeparatorDescription\": \"Digite o caractere usado para delimitar colunas no arquivo de entrada CSV.\",\n    \"deleteLinesWithNoData\": \"Excluir linhas sem dados\",\n    \"deleteLinesWithNoDataDescription\": \"Remova linhas vazias do arquivo de entrada CSV.\",\n    \"description\": \"Basta enviar seu arquivo CSV no formulário abaixo e esta ferramenta verificará automaticamente se nenhuma das linhas ou colunas contém valores ausentes. Nas opções da ferramenta, você pode ajustar o formato do arquivo de entrada (especifique o delimitador, o caractere de aspas e o caractere de comentário). Além disso, você pode ativar a verificação de valores vazios, ignorar linhas vazias e definir um limite para o número de mensagens de erro na saída.\",\n    \"findEmptyValues\": \"Encontrar valores vazios\",\n    \"findEmptyValuesDescription\": \"Exibir uma mensagem sobre campos CSV que estão vazios (não são campos ausentes, mas campos que não contêm nada).\",\n    \"inputTitle\": \"Entrada CSV\",\n    \"limitNumberOfMessages\": \"Limite de número de mensagens\",\n    \"messageLimitDescription\": \"Defina o limite do número de mensagens na saída.\",\n    \"quoteCharacterDescription\": \"Digite o caractere de aspas usado para citar os campos de entrada CSV.\",\n    \"resultTitle\": \"Status do CSV\",\n    \"shortDescription\": \"Encontre rapidamente linhas e colunas em CSV que não tenham valores.\",\n    \"title\": \"Encontre registros CSV incompletos\",\n    \"toolInfo\": {\n      \"title\": \"O que é um {{title}}?\"\n    }\n  },\n  \"insertCsvColumns\": {\n    \"appendColumns\": \"Adicionar colunas\",\n    \"commentCharacterDescription\": \"Insira o caractere que indica o início de uma linha de comentário. Linhas que começam com este símbolo serão ignoradas.\",\n    \"csvOptions\": \"Opções CSV\",\n    \"csvSeparator\": \"Separador CSV\",\n    \"csvToInsert\": \"CSV para inserir\",\n    \"csvToInsertDescription\": \"Insira uma ou mais colunas que deseja inserir no CSV. O caractere usado para delimitar as colunas deve ser o mesmo que o do arquivo CSV de entrada. Obs.: Linhas em branco serão ignoradas.\",\n    \"customFillDescription\": \"Se o arquivo CSV de entrada estiver incompleto (valores ausentes), adicione campos vazios ou símbolos personalizados aos registros para criar um CSV bem formado?\",\n    \"customFillValueDescription\": \"Use este valor personalizado para preencher campos ausentes. (Funciona apenas com o modo \\\"Valores Personalizados\\\" acima.)\",\n    \"customPosition\": \"Posição personalizada\",\n    \"customPositionOptionsDescription\": \"Selecione o método para inserir as colunas no arquivo CSV.\",\n    \"description\": \"Adicione novas colunas aos dados CSV em posições especificadas.\",\n    \"fillWithCustomValues\": \"Preencher com valores alfandegários\",\n    \"fillWithEmptyValues\": \"Preencher com valores vazios\",\n    \"headerName\": \"Nome do cabeçalho\",\n    \"headerNameDescription\": \"Cabeçalho da coluna depois da qual você deseja inserir colunas.\",\n    \"inputTitle\": \"Entrada CSV\",\n    \"insertingPositionDescription\": \"Especifique onde inserir as colunas no arquivo CSV.\",\n    \"position\": \"Posição\",\n    \"positionOptions\": \"Opções de posição\",\n    \"prependColumns\": \"Prefixar colunas\",\n    \"quoteCharDescription\": \"Digite o caractere de aspas usado para citar os campos de entrada CSV.\",\n    \"resultTitle\": \"Saída CSV\",\n    \"rowNumberDescription\": \"Número da coluna depois da qual você deseja inserir colunas.\",\n    \"separatorDescription\": \"Digite o caractere usado para delimitar colunas no arquivo de entrada CSV.\",\n    \"shortDescription\": \"Insira rapidamente uma ou mais colunas novas em qualquer lugar de um arquivo CSV.\",\n    \"title\": \"Inserir colunas CSV\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite inserir novas colunas em dados CSV em posições específicas. Você pode acrescentar, anexar ou inserir colunas em posições personalizadas com base em nomes de cabeçalho ou números de coluna.\",\n      \"title\": \"Inserir colunas CSV\"\n    }\n  },\n  \"swapCsvColumns\": {\n    \"description\": \"Basta carregar seu arquivo CSV no formulário abaixo, especificar as colunas a serem trocadas e a ferramenta alterará automaticamente as posições das colunas especificadas no arquivo de saída. Nas opções da ferramenta, você pode especificar as posições ou nomes das colunas que deseja trocar, bem como corrigir dados incompletos e, opcionalmente, remover registros vazios e registros comentados.\",\n    \"longDescription\": \"Esta ferramenta reorganiza dados CSV trocando as posições de suas colunas. Trocar colunas pode melhorar a legibilidade de um arquivo CSV, colocando dados usados com frequência juntos ou na frente para facilitar a comparação e a edição de dados. Por exemplo, você pode trocar a primeira coluna com a última ou trocar a segunda coluna com a terceira. Para trocar colunas com base em suas posições, selecione a opção\",\n    \"shortDescription\": \"Reordene as colunas CSV.\",\n    \"title\": \"Trocar colunas CSV\"\n  },\n  \"transposeCsv\": {\n    \"description\": \"Basta enviar seu arquivo CSV no formulário abaixo e esta ferramenta o transporá automaticamente. Nas opções da ferramenta, você pode especificar o caractere que inicia as linhas de comentário no CSV para removê-las. Além disso, se o CSV estiver incompleto (com valores ausentes), você pode substituí-los pelo caractere vazio ou por um caractere personalizado.\",\n    \"longDescription\": \"Esta ferramenta transpõe valores separados por vírgula (CSV). Ela trata o CSV como uma matriz de dados e inverte todos os elementos na diagonal principal. A saída contém os mesmos dados CSV que a entrada, mas agora todas as linhas se tornaram colunas e todas as colunas se tornaram linhas. Após a transposição, o arquivo CSV terá dimensões opostas. Por exemplo, se o arquivo de entrada tiver 4 colunas e 3 linhas, o arquivo de saída terá 3 colunas e 4 linhas. Durante a conversão, o programa também limpa os dados de linhas desnecessárias e corrige dados incompletos. Especificamente, a ferramenta exclui automaticamente todos os registros e comentários vazios que começam com um caractere específico, que você pode definir na opção. Além disso, nos casos em que os dados CSV são corrompidos ou perdidos, o utilitário completa o arquivo com campos vazios ou campos personalizados que podem ser especificados nas opções. CSV-abulous!\",\n    \"shortDescription\": \"Transponha rapidamente um arquivo CSV.\",\n    \"title\": \"Transpor CSV\"\n  },\n  \"tsvToJson\": {\n    \"description\": \"Converta dados TSV (Valores Separados por Tabulação) para o formato JSON. Transforme dados tabulares em objetos JSON estruturados.\",\n    \"shortDescription\": \"Converter TSV para o formato JSON\",\n    \"title\": \"TSV para JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/image.json",
    "content": "{\n  \"changeColors\": {\n    \"description\": \"Mundo\",\n    \"shortDescription\": \"Troque cores rapidamente em uma imagem\",\n    \"title\": \"Alterar cores na imagem\"\n  },\n  \"changeOpacity\": {\n    \"description\": \"Ajuste facilmente a transparência das suas imagens. Basta carregar a sua imagem, usar o controle deslizante para definir o nível de opacidade desejado entre 0 (totalmente transparente) e 1 (totalmente opaco) e baixar a imagem modificada.\",\n    \"shortDescription\": \"Ajustar a transparência das imagens\",\n    \"title\": \"Alterar opacidade da imagem\"\n  },\n  \"compress\": {\n    \"compressedSize\": \"Tamanho compactado\",\n    \"compressionOptions\": \"Opções de compressão\",\n    \"description\": \"Reduza o tamanho do arquivo de imagem, mantendo a qualidade.\",\n    \"failedToCompress\": \"Falha ao compactar a imagem. Tente novamente.\",\n    \"fileSizes\": \"Tamanhos de arquivo\",\n    \"inputTitle\": \"Imagem de entrada\",\n    \"maxFileSizeDescription\": \"Tamanho máximo do arquivo em megabytes\",\n    \"originalSize\": \"Tamanho original\",\n    \"qualityDescription\": \"Porcentagem de qualidade da imagem (menor significa menor tamanho de arquivo)\",\n    \"resultTitle\": \"Imagem compactada\",\n    \"shortDescription\": \"Compacte imagens para reduzir o tamanho do arquivo, mantendo uma qualidade razoável.\",\n    \"title\": \"Comprimir imagem\"\n  },\n  \"compressPng\": {\n    \"description\": \"Este é um programa que compacta imagens PNG. Assim que você colar a imagem PNG na área de entrada, o programa a compactará e exibirá o resultado na área de saída. Nas opções, você pode ajustar o nível de compactação, bem como encontrar os tamanhos de arquivo de imagem antigos e novos.\",\n    \"shortDescription\": \"Comprimir rapidamente um PNG\",\n    \"title\": \"Comprimir png\"\n  },\n  \"convertJgpToPng\": {\n    \"description\": \"Converta rapidamente suas imagens JPG para PNG. Basta importar sua imagem PNG no editor à esquerda.\",\n    \"shortDescription\": \"Converta rapidamente suas imagens JPG para PNG\",\n    \"title\": \"Converter JPG para PNG\"\n  },\n  \"convertToJpg\": {\n    \"description\": \"Converta vários formatos de imagem (PNG, GIF, TIF, PSD, SVG, WEBP, HEIC, RAW) para JPG com configurações personalizáveis de qualidade e cor de fundo.\",\n    \"shortDescription\": \"Converta imagens para JPG com controle de qualidade\",\n    \"title\": \"Converter imagens para JPG\"\n  },\n  \"createTransparent\": {\n    \"description\": \"Mundo\",\n    \"shortDescription\": \"Tornar uma imagem transparente rapidamente\",\n    \"title\": \"Criar PNG transparente\"\n  },\n  \"crop\": {\n    \"description\": \"Corte imagens para remover áreas indesejadas.\",\n    \"inputTitle\": \"Imagem de entrada\",\n    \"resultTitle\": \"Imagem recortada\",\n    \"shortDescription\": \"Corte imagens rapidamente.\",\n    \"title\": \"Cortar imagem\"\n  },\n  \"editor\": {\n    \"description\": \"Editor de imagens avançado com ferramentas para cortar, girar, anotar, ajustar cores e adicionar marcas d'água. Edite suas imagens com ferramentas profissionais diretamente no seu navegador.\",\n    \"shortDescription\": \"Edite imagens com ferramentas e recursos avançados\",\n    \"title\": \"Editor de imagens\"\n  },\n  \"imageToText\": {\n    \"description\": \"Extraia texto de imagens (JPG, PNG) usando reconhecimento óptico de caracteres (OCR).\",\n    \"shortDescription\": \"Extraia texto de imagens usando OCR.\",\n    \"title\": \"Imagem para texto (OCR)\"\n  },\n  \"qrCode\": {\n    \"description\": \"Gere códigos QR para diferentes tipos de dados: URL, texto, e-mail, telefone, SMS, WiFi, vCard e muito mais.\",\n    \"shortDescription\": \"Crie códigos QR personalizados para vários formatos de dados.\",\n    \"title\": \"Gerador de código QR\"\n  },\n  \"removeBackground\": {\n    \"description\": \"Mundo\",\n    \"shortDescription\": \"Remover fundos de imagens automaticamente\",\n    \"title\": \"Remover fundo da imagem\"\n  },\n  \"resize\": {\n    \"description\": \"Redimensione imagens para dimensões diferentes.\",\n    \"dimensionType\": \"Tipo de dimensão\",\n    \"heightDescription\": \"Altura (em pixels)\",\n    \"inputTitle\": \"Imagem de entrada\",\n    \"maintainAspectRatio\": \"Manter proporção de aspecto\",\n    \"maintainAspectRatioDescription\": \"Manter a proporção original da imagem.\",\n    \"percentage\": \"Percentagem\",\n    \"percentageDescription\": \"Porcentagem do tamanho original (por exemplo, 50 para metade do tamanho, 200 para o dobro do tamanho)\",\n    \"resizeByPercentage\": \"Redimensionar por porcentagem\",\n    \"resizeByPercentageDescription\": \"Redimensione especificando uma porcentagem do tamanho original.\",\n    \"resizeByPixels\": \"Redimensionar por pixels\",\n    \"resizeByPixelsDescription\": \"Redimensione especificando dimensões em pixels.\",\n    \"resizeMethod\": \"Método de redimensionamento\",\n    \"resultTitle\": \"Imagem redimensionada\",\n    \"setHeight\": \"Altura definida\",\n    \"setHeightDescription\": \"Especifique a altura em pixels e calcule a largura com base na proporção.\",\n    \"setWidth\": \"Definir largura\",\n    \"setWidthDescription\": \"Especifique a largura em pixels e calcule a altura com base na proporção.\",\n    \"shortDescription\": \"Redimensione imagens facilmente.\",\n    \"title\": \"Redimensionar imagem\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite redimensionar imagens JPG, PNG, SVG ou GIF. Você pode redimensionar especificando as dimensões em pixels ou em porcentagem, com opções para manter a proporção original.\",\n      \"title\": \"Redimensionar imagem\"\n    },\n    \"widthDescription\": \"Largura (em pixels)\"\n  },\n  \"rotate\": {\n    \"description\": \"Girar uma imagem em um ângulo especificado.\",\n    \"shortDescription\": \"Gire uma imagem facilmente.\",\n    \"title\": \"Girar imagem\"\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/json.json",
    "content": "{\n  \"comparison\": {\n    \"description\": \"Compare dois objetos JSON para identificar diferenças na estrutura e nos valores.\",\n    \"shortDescription\": \"Encontre diferenças entre dois objetos JSON\",\n    \"title\": \"Comparar JSON\"\n  },\n  \"escapeJson\": {\n    \"description\": \"Escape caracteres especiais em strings JSON. Converta dados JSON para um formato de escape adequado para transmissão ou armazenamento seguro.\",\n    \"shortDescription\": \"Escape de caracteres especiais em JSON\",\n    \"title\": \"Escape JSON\"\n  },\n  \"jsonToXml\": {\n    \"description\": \"Converta dados JSON para o formato XML. Transforme objetos JSON estruturados em documentos XML bem-formados.\",\n    \"shortDescription\": \"Converter JSON para o formato XML\",\n    \"title\": \"JSON para XML\"\n  },\n  \"minify\": {\n    \"description\": \"Remova todos os espaços em branco desnecessários do JSON.\",\n    \"inputTitle\": \"Entrada JSON\",\n    \"resultTitle\": \"JSON minificado\",\n    \"shortDescription\": \"Minifique JSON removendo espaços em branco\",\n    \"title\": \"Minificar JSON\",\n    \"toolInfo\": {\n      \"description\": \"A minificação de JSON é o processo de remover todos os caracteres de espaço em branco desnecessários dos dados JSON, mantendo sua validade. Isso inclui a remoção de espaços, quebras de linha e recuos desnecessários para que o JSON seja analisado corretamente. A minificação reduz o tamanho dos dados JSON, tornando-os mais eficientes para armazenamento e transmissão, mantendo exatamente a mesma estrutura de dados e valores.\",\n      \"title\": \"O que é minificação JSON?\"\n    }\n  },\n  \"prettify\": {\n    \"description\": \"Formate JSON com recuo e espaçamento adequados.\",\n    \"indentation\": \"Recuo\",\n    \"inputTitle\": \"Entrada JSON\",\n    \"resultTitle\": \"JSON embelezado\",\n    \"shortDescription\": \"Formate e embeleze o código JSON\",\n    \"title\": \"Embeleze JSON\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite formatar dados JSON com recuo e espaçamento adequados, tornando-os mais legíveis e fáceis de trabalhar.\",\n      \"title\": \"Embeleze JSON\"\n    },\n    \"useSpaces\": \"Use Espaços\",\n    \"useSpacesDescription\": \"Recuar a saída com espaços\",\n    \"useTabs\": \"Usar guias\",\n    \"useTabsDescription\": \"Recuar a saída com tabulações.\"\n  },\n  \"stringify\": {\n    \"description\": \"Converta objetos JavaScript para o formato de string JSON. Serialize estruturas de dados em strings JSON para armazenamento ou transmissão.\",\n    \"shortDescription\": \"Converter objetos em string JSON\",\n    \"title\": \"Stringify JSON\"\n  },\n  \"validateJson\": {\n    \"description\": \"Verifique se JSON é válido e bem formado.\",\n    \"inputTitle\": \"Entrada JSON\",\n    \"invalidJson\": \"❌ {{error}}\",\n    \"resultTitle\": \"Resultado da Validação\",\n    \"shortDescription\": \"Validar código JSON para erros\",\n    \"title\": \"Validar JSON\",\n    \"toolInfo\": {\n      \"description\": \"JSON (JavaScript Object Notation) é um formato leve para troca de dados. A validação JSON garante que a estrutura dos dados esteja em conformidade com o padrão JSON. Um objeto JSON válido deve ter: - Nomes de propriedades entre aspas duplas. - Chaves {} devidamente balanceadas. - Sem vírgulas finais após o último par chave-valor. - Aninhamento correto de objetos e matrizes. Esta ferramenta verifica o JSON de entrada e fornece feedback para ajudar a identificar e corrigir erros comuns.\",\n      \"title\": \"O que é validação JSON?\"\n    },\n    \"validJson\": \"✅ JSON válido\"\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/list.json",
    "content": "{\n  \"duplicate\": {\n    \"concatenate\": \"Concatenar\",\n    \"concatenateDescription\": \"Concatenar cópias (se desmarcado, os itens serão entrelaçados)\",\n    \"copyDescription\": \"Número de cópias (pode ser fracionário)\",\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para duplicar itens de lista. Insira sua lista e especifique critérios de duplicação para criar cópias dos itens. Perfeito para expansão de dados, testes ou criação de padrões repetidos.\",\n    \"duplicationOptions\": \"Opções de duplicação\",\n    \"error\": \"Erro\",\n    \"example1Description\": \"Este exemplo mostra como duplicar uma lista de palavras.\",\n    \"example1Title\": \"Duplicação simples\",\n    \"example2Description\": \"Este exemplo mostra como duplicar uma lista na ordem inversa.\",\n    \"example2Title\": \"Duplicação reversa\",\n    \"example3Description\": \"Este exemplo mostra como entrelaçar itens em vez de concatená-los.\",\n    \"example3Title\": \"Itens entrelaçados\",\n    \"example4Description\": \"Este exemplo mostra como duplicar uma lista com um número fracionário de cópias.\",\n    \"example4Title\": \"Duplicação fracionária\",\n    \"examples\": {\n      \"fractional\": {\n        \"description\": \"Este exemplo mostra como duplicar uma lista com um número fracionário de cópias.\",\n        \"title\": \"Duplicação fracionária\"\n      },\n      \"interweave\": {\n        \"description\": \"Este exemplo mostra como entrelaçar itens em vez de concatená-los.\",\n        \"title\": \"Itens entrelaçados\"\n      },\n      \"reverse\": {\n        \"description\": \"Este exemplo mostra como duplicar uma lista na ordem inversa.\",\n        \"title\": \"Duplicação reversa\"\n      },\n      \"simple\": {\n        \"description\": \"Este exemplo mostra como duplicar uma lista de palavras.\",\n        \"title\": \"Duplicação simples\"\n      }\n    },\n    \"inputTitle\": \"Lista de Entrada\",\n    \"joinSeparatorDescription\": \"Separador para unir a lista duplicada\",\n    \"resultTitle\": \"Lista Duplicada\",\n    \"reverse\": \"Reverter\",\n    \"reverseDescription\": \"Reverter os itens duplicados\",\n    \"shortDescription\": \"Itens de lista duplicados com critérios especificados\",\n    \"splitByRegex\": \"Dividir por expressão regular\",\n    \"splitBySymbol\": \"Dividido por símbolo\",\n    \"splitOptions\": \"Opções de divisão\",\n    \"splitSeparatorDescription\": \"Separador para dividir a lista\",\n    \"title\": \"Duplicado\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite duplicar itens em uma lista. Você pode especificar o número de cópias (incluindo valores fracionários), controlar se os itens serão concatenados ou entrelaçados e até mesmo reverter os itens duplicados. É útil para criar padrões repetidos, gerar dados de teste ou expandir listas com conteúdo previsível.\",\n      \"title\": \"Duplicação de lista\"\n    },\n    \"unknownError\": \"Ocorreu um erro desconhecido\",\n    \"validation\": {\n      \"copyMustBeNumber\": \"O número de cópias deve ser um número\",\n      \"copyMustBePositive\": \"O número de cópias deve ser positivo\",\n      \"copyRequired\": \"Número de cópias necessário\",\n      \"joinSeparatorRequired\": \"O separador de junção é necessário\",\n      \"separatorRequired\": \"O separador é necessário\"\n    }\n  },\n  \"findMostPopular\": {\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para encontrar os itens mais populares em uma lista. Insira sua lista e obtenha instantaneamente os itens que aparecem com mais frequência. Perfeito para análise de dados, identificação de tendências ou busca por elementos comuns.\",\n    \"displayFormatDescription\": \"Como exibir os itens de lista mais populares?\",\n    \"displayOptions\": {\n      \"count\": \"Mostrar contagem de itens\",\n      \"percentage\": \"Mostrar porcentagem do item\",\n      \"total\": \"Mostrar total do item\"\n    },\n    \"extractListItems\": \"Como extrair itens de lista?\",\n    \"ignoreItemCase\": \"Ignorar maiúsculas e minúsculas\",\n    \"ignoreItemCaseDescription\": \"Compare todos os itens da lista em letras minúsculas.\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"itemComparison\": \"Comparação de itens\",\n    \"outputFormat\": \"Formato de saída do item principal\",\n    \"removeEmptyItems\": \"Remover itens vazios\",\n    \"removeEmptyItemsDescription\": \"Ignore itens vazios da comparação.\",\n    \"resultTitle\": \"Itens mais populares\",\n    \"shortDescription\": \"Encontre os itens mais frequentes\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Classificar em ordem alfabética\",\n      \"count\": \"Classificar por contagem\"\n    },\n    \"sortingMethodDescription\": \"Selecione um método de classificação.\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Delimite itens da lista de entrada com uma expressão regular.\",\n        \"title\": \"Use um Regex para Divisão\"\n      },\n      \"symbol\": {\n        \"description\": \"Delimitar itens da lista de entrada com um caractere.\",\n        \"title\": \"Use um símbolo para dividir\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Defina um símbolo delimitador ou uma expressão regular.\",\n    \"title\": \"Encontre os mais populares\",\n    \"trimItems\": \"Cortar itens da lista principal\",\n    \"trimItemsDescription\": \"Remova os espaços iniciais e finais antes de comparar itens\"\n  },\n  \"findUnique\": {\n    \"caseSensitiveItems\": \"Itens que diferenciam maiúsculas de minúsculas\",\n    \"caseSensitiveItemsDescription\": \"Produza itens com maiúsculas e minúsculas diferentes como elementos exclusivos na lista.\",\n    \"delimiterDescription\": \"Defina um símbolo delimitador ou uma expressão regular.\",\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para encontrar itens únicos em uma lista. Insira sua lista e obtenha instantaneamente todos os valores únicos, com as duplicatas removidas. Perfeito para limpeza de dados, desduplicação ou busca de elementos distintos.\",\n    \"findAbsolutelyUniqueItems\": \"Encontre itens absolutamente únicos\",\n    \"findAbsolutelyUniqueItemsDescription\": \"Exibe somente os itens da lista que existem em uma única cópia.\",\n    \"inputListDelimiter\": \"Delimitador de lista de entrada\",\n    \"inputTitle\": \"Lista de Entrada\",\n    \"outputListDelimiter\": \"Delimitador da lista de saída\",\n    \"resultTitle\": \"Itens Únicos\",\n    \"shortDescription\": \"Encontre itens exclusivos em uma lista\",\n    \"skipEmptyItems\": \"Pular itens vazios\",\n    \"skipEmptyItemsDescription\": \"Não inclua os itens da lista vazia na saída.\",\n    \"title\": \"Encontre algo único\",\n    \"trimItems\": \"Itens da lista de corte\",\n    \"trimItemsDescription\": \"Remova os espaços iniciais e finais antes de comparar itens.\",\n    \"uniqueItemOptions\": \"Opções de itens exclusivos\"\n  },\n  \"group\": {\n    \"deleteEmptyItems\": \"Excluir itens vazios\",\n    \"deleteEmptyItemsDescription\": \"Ignore itens vazios e não os inclua nos grupos.\",\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para agrupar itens de lista. Insira sua lista e especifique critérios de agrupamento para organizar os itens em grupos lógicos. Perfeito para categorizar dados, organizar informações ou criar listas estruturadas. Suporta separadores personalizados e diversas opções de agrupamento.\",\n    \"emptyItemsAndPadding\": \"Itens vazios e preenchimento\",\n    \"groupNumberDescription\": \"Número de itens em um grupo\",\n    \"groupSeparatorDescription\": \"Caractere separador de grupo\",\n    \"groupSizeAndSeparators\": \"Tamanho do grupo e separadores\",\n    \"inputItemSeparator\": \"Separador de itens de entrada\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"itemSeparatorDescription\": \"Caractere separador de itens\",\n    \"leftWrapDescription\": \"Símbolo de envoltório esquerdo do grupo.\",\n    \"padNonFullGroups\": \"Grupos não completos\",\n    \"padNonFullGroupsDescription\": \"Preencha grupos não completos com um item personalizado (insira abaixo).\",\n    \"paddingCharDescription\": \"Use este caractere ou item para preencher grupos não completos.\",\n    \"resultTitle\": \"Itens agrupados\",\n    \"rightWrapDescription\": \"Símbolo de envoltório direito do grupo.\",\n    \"shortDescription\": \"Agrupar itens da lista por propriedades comuns\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Delimite itens da lista de entrada com uma expressão regular.\",\n        \"title\": \"Use um Regex para Divisão\"\n      },\n      \"symbol\": {\n        \"description\": \"Delimitar itens da lista de entrada com um caractere.\",\n        \"title\": \"Use um símbolo para dividir\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Defina um símbolo delimitador ou uma expressão regular.\",\n    \"title\": \"Grupo\"\n  },\n  \"reverse\": {\n    \"description\": \"Este é um aplicativo super simples baseado em navegador que imprime todos os itens da lista em ordem inversa. Os itens de entrada podem ser separados por qualquer símbolo e você também pode alterar o separador dos itens da lista em ordem inversa.\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"itemSeparator\": \"Separador de Itens\",\n    \"itemSeparatorDescription\": \"Defina um símbolo delimitador ou uma expressão regular.\",\n    \"outputListOptions\": \"Opções da lista de saída\",\n    \"outputSeparatorDescription\": \"Separador de itens da lista de saída.\",\n    \"resultTitle\": \"Lista invertida\",\n    \"shortDescription\": \"Inverter uma lista rapidamente\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Delimite itens da lista de entrada com uma expressão regular.\",\n        \"title\": \"Use um Regex para Divisão\"\n      },\n      \"symbol\": {\n        \"description\": \"Delimitar itens da lista de entrada com um caractere.\",\n        \"title\": \"Use um símbolo para dividir\"\n      }\n    },\n    \"splitterMode\": \"Modo divisor\",\n    \"title\": \"Reverter\",\n    \"toolInfo\": {\n      \"description\": \"Com este utilitário, você pode inverter a ordem dos itens em uma lista. O utilitário primeiro divide a lista de entrada em itens individuais e, em seguida, itera entre eles, do último item para o primeiro, imprimindo cada item na saída durante a iteração. A lista de entrada pode conter qualquer coisa que possa ser representada como dados textuais, o que inclui dígitos, números, strings, palavras, frases, etc. O separador de itens de entrada também pode ser uma expressão regular. Por exemplo, a expressão regular /[;,]/ permitirá que você use itens separados por vírgula ou ponto e vírgula. Os delimitadores dos itens da lista de entrada e saída podem ser personalizados nas opções. Por padrão, as listas de entrada e saída são separadas por vírgula. Listabulous!\",\n      \"title\": \"O que é um inversor de lista?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para rotacionar itens de lista. Insira sua lista e especifique o valor de rotação para deslocar os itens em um número específico de posições. Perfeito para manipulação de dados, deslocamentos circulares ou reordenação de listas.\",\n    \"shortDescription\": \"Girar itens da lista por posições especificadas\",\n    \"title\": \"Girar\"\n  },\n  \"shuffle\": {\n    \"delimiterDescription\": \"Defina um símbolo delimitador ou uma expressão regular.\",\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para embaralhar itens de lista. Insira sua lista e obtenha instantaneamente uma versão aleatória com itens em ordem aleatória. Perfeito para criar variedade, testar aleatoriedade ou misturar dados ordenados.\",\n    \"inputListSeparator\": \"Separador de lista de entrada\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"joinSeparatorDescription\": \"Use este separador na lista aleatória.\",\n    \"outputLengthDescription\": \"Produzir esta quantidade de itens aleatórios\",\n    \"resultTitle\": \"Lista embaralhada\",\n    \"shortDescription\": \"Randomize a ordem dos itens da lista\",\n    \"shuffledListLength\": \"Comprimento da lista embaralhada\",\n    \"shuffledListSeparator\": \"Separador de lista embaralhada\",\n    \"title\": \"Embaralhar\"\n  },\n  \"sort\": {\n    \"caseSensitive\": \"Classificação com diferenciação entre maiúsculas e minúsculas\",\n    \"caseSensitiveDescription\": \"Classifique itens em letras maiúsculas e minúsculas separadamente. Letras maiúsculas precedem letras minúsculas em uma lista crescente. (Funciona apenas no modo de classificação alfabética.)\",\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para classificar itens de lista. Insira sua lista e especifique critérios de classificação para organizar os itens em ordem crescente ou decrescente. Perfeito para organização de dados, processamento de texto ou criação de listas ordenadas.\",\n    \"inputItemSeparator\": \"Separador de itens de entrada\",\n    \"inputTitle\": \"Lista de entrada\",\n    \"joinSeparatorDescription\": \"Use este símbolo para unir itens em uma lista classificada.\",\n    \"orderDescription\": \"Selecione uma ordem de classificação.\",\n    \"orderOptions\": {\n      \"decreasing\": \"Ordem decrescente\",\n      \"increasing\": \"Ordem crescente\"\n    },\n    \"removeDuplicates\": \"Remover duplicatas\",\n    \"removeDuplicatesDescription\": \"Excluir itens duplicados da lista.\",\n    \"resultTitle\": \"Lista ordenada\",\n    \"shortDescription\": \"Classificar itens da lista na ordem especificada\",\n    \"sortMethod\": \"Método de classificação\",\n    \"sortMethodDescription\": \"Selecione um método de classificação.\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Classificar em ordem alfabética\",\n      \"length\": \"Classificar por comprimento\",\n      \"numeric\": \"Classificar numericamente\"\n    },\n    \"sortedItemProperties\": \"Propriedades de itens classificados\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Delimite itens da lista de entrada com uma expressão regular.\",\n        \"title\": \"Use um Regex para Divisão\"\n      },\n      \"symbol\": {\n        \"description\": \"Delimitar itens da lista de entrada com um caractere.\",\n        \"title\": \"Use um símbolo para dividir\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Defina um símbolo delimitador ou uma expressão regular.\",\n    \"title\": \"Organizar\"\n  },\n  \"truncate\": {\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para truncar listas. Insira sua lista e especifique o número máximo de itens a serem mantidos. Perfeito para processamento de dados, gerenciamento de listas ou limitação de tamanho de conteúdo.\",\n    \"shortDescription\": \"Truncar lista para um número específico de itens\",\n    \"title\": \"Truncar\"\n  },\n  \"unwrap\": {\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para descompactar itens de lista. Insira sua lista encapsulada e especifique critérios de descompactação para simplificar os itens organizados. Perfeito para processamento de dados, manipulação de texto ou extração de conteúdo de listas estruturadas.\",\n    \"shortDescription\": \"Desembrulhe itens de lista do formato estruturado\",\n    \"title\": \"Desembrulhar\"\n  },\n  \"wrap\": {\n    \"description\": \"Adicione texto antes e depois de cada item da lista.\",\n    \"inputTitle\": \"Lista de Entrada\",\n    \"joinSeparatorDescription\": \"Separador para unir a lista encapsulada\",\n    \"leftTextDescription\": \"Texto para adicionar antes de cada item\",\n    \"removeEmptyItems\": \"Remover itens vazios\",\n    \"resultTitle\": \"Lista encapsulada\",\n    \"rightTextDescription\": \"Texto para adicionar após cada item\",\n    \"shortDescription\": \"Encapsule os itens da lista com critérios especificados\",\n    \"splitByRegex\": \"Dividir por expressão regular\",\n    \"splitBySymbol\": \"Dividido por símbolo\",\n    \"splitOptions\": \"Opções de divisão\",\n    \"splitSeparatorDescription\": \"Separador para dividir a lista\",\n    \"title\": \"Enrolar\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite adicionar texto antes e depois de cada item de uma lista. Você pode especificar textos diferentes para os lados esquerdo e direito e controlar como a lista é processada. É útil para adicionar aspas, colchetes ou outras formatações aos itens da lista, preparar dados para diferentes formatos ou criar texto estruturado.\",\n      \"title\": \"Encapsulamento de lista\"\n    },\n    \"wrapOptions\": \"Opções de envoltório\"\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/number.json",
    "content": "{\n  \"arithmeticSequence\": {\n    \"commonDifferenceDescription\": \"Diferença comum entre os termos (d)\",\n    \"description\": \"Gere sequências aritméticas com parâmetros personalizáveis.\",\n    \"firstTermDescription\": \"Primeiro termo da sequência (a₁)\",\n    \"numberOfTermsDescription\": \"Número de termos a gerar (n)\",\n    \"outputFormat\": \"Formato de saída\",\n    \"resultTitle\": \"Sequência Gerada\",\n    \"separatorDescription\": \"Separador entre termos\",\n    \"sequenceParameters\": \"Parâmetros de sequência\",\n    \"shortDescription\": \"Gerar sequências aritméticas\",\n    \"title\": \"Sequência Aritmética\",\n    \"toolInfo\": {\n      \"description\": \"Uma progressão aritmética é uma sequência de números em que a diferença entre cada termo consecutivo é constante. Essa diferença constante é chamada de diferença comum. Dado o primeiro termo (a₁) e a diferença comum (d), cada termo pode ser encontrado somando a diferença comum ao termo anterior.\",\n      \"title\": \"O que é uma sequência aritmética?\"\n    }\n  },\n  \"generate\": {\n    \"arithmeticSequenceOption\": \"Opção de sequência aritmética\",\n    \"description\": \"Gere uma sequência de números com parâmetros personalizáveis.\",\n    \"numberOfElementsDescription\": \"Número de elementos em sequência.\",\n    \"resultTitle\": \"Números gerados\",\n    \"separator\": \"Separador\",\n    \"separatorDescription\": \"Separe os elementos na sequência aritmética por este caractere.\",\n    \"shortDescription\": \"Gerar números aleatórios em intervalos especificados\",\n    \"startSequenceDescription\": \"Inicie a sequência a partir deste número.\",\n    \"stepDescription\": \"Aumente cada elemento por esta quantidade\",\n    \"title\": \"Gerar\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite gerar uma sequência de números com parâmetros personalizáveis. Você pode especificar o valor inicial, o tamanho do passo e o número de elementos.\",\n      \"title\": \"Gerar números\"\n    }\n  },\n  \"ohmsLaw\": {\n    \"description\": \"Calcula tensão, corrente e resistência\",\n    \"longDescription\": \"Esta calculadora aplica a Lei de Ohm (V = I × R) para determinar qualquer um dos três parâmetros elétricos quando os outros dois são conhecidos. A Lei de Ohm é um princípio fundamental em engenharia elétrica que descreve a relação entre tensão (V), corrente (I) e resistência (R). Esta ferramenta é essencial para entusiastas de eletrônica, engenheiros elétricos e estudantes que trabalham com circuitos, para que possam resolver rapidamente valores desconhecidos em seus projetos elétricos.\",\n    \"shortDescription\": \"Calcular tensão, corrente ou resistência em circuitos elétricos usando a Lei de Ohm\",\n    \"title\": \"Lei de Ohm\"\n  },\n  \"randomNumberGenerator\": {\n    \"description\": \"Gere números aleatórios dentro de um intervalo especificado com opções personalizáveis.\",\n    \"error\": {\n      \"generationFailed\": \"Falha ao gerar números aleatórios. Verifique seus parâmetros de entrada.\"\n    },\n    \"info\": {\n      \"description\": \"Um gerador de números aleatórios cria números imprevisíveis dentro de um intervalo especificado. Esta ferramenta utiliza geração de números aleatórios criptograficamente segura para garantir resultados verdadeiramente aleatórios. Útil para simulações, jogos, amostragem estatística e cenários de teste.\",\n      \"title\": \"O que é um gerador de números aleatórios?\"\n    },\n    \"longDescription\": \"Gere números aleatórios dentro de um intervalo especificado com opções para números inteiros ou decimais, permitindo ou impedindo duplicatas e classificando os resultados. Perfeito para simulações, testes, jogos e análises estatísticas.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDecimals\": {\n          \"description\": \"Gerar números decimais em vez de inteiros\",\n          \"title\": \"Permitir números decimais\"\n        },\n        \"allowDuplicates\": {\n          \"description\": \"Permitir que o mesmo número apareça várias vezes\",\n          \"title\": \"Permitir duplicatas\"\n        },\n        \"countDescription\": \"Número de números aleatórios a serem gerados (1-10.000)\",\n        \"sortResults\": {\n          \"description\": \"Classifique os números gerados em ordem crescente\",\n          \"title\": \"Classificar resultados\"\n        },\n        \"title\": \"Opções de geração\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Caractere(s) para separar os números gerados\",\n        \"title\": \"Configurações de saída\"\n      },\n      \"range\": {\n        \"maxDescription\": \"Valor máximo (inclusive)\",\n        \"minDescription\": \"Valor mínimo (inclusive)\",\n        \"title\": \"Configurações de alcance\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Contar\",\n      \"hasDuplicates\": \"Contém duplicatas\",\n      \"isSorted\": \"Classificado\",\n      \"range\": \"Faixa\",\n      \"title\": \"Números aleatórios gerados\"\n    },\n    \"shortDescription\": \"Gerar números aleatórios em intervalos personalizados\",\n    \"title\": \"Gerador de números aleatórios\"\n  },\n  \"randomPortGenerator\": {\n    \"description\": \"Gere portas de rede aleatórias dentro de intervalos especificados com opções personalizáveis.\",\n    \"error\": {\n      \"generationFailed\": \"Falha ao gerar portas aleatórias. Verifique seus parâmetros de entrada.\"\n    },\n    \"info\": {\n      \"description\": \"Um gerador de portas aleatório cria números de portas de rede imprevisíveis dentro de intervalos especificados. Esta ferramenta segue os padrões de números de portas da IANA e inclui a identificação de serviços comuns. Útil para desenvolvimento, testes, configuração de rede e prevenção de conflitos de portas.\",\n      \"title\": \"O que é um gerador de portas aleatórias?\"\n    },\n    \"longDescription\": \"Gere portas de rede aleatórias dentro de intervalos especificados (conhecidas, registradas, dinâmicas ou personalizadas). Ideal para desenvolvimento, testes e configuração de rede. Inclui identificação de serviço de porta para portas comuns.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDuplicates\": {\n          \"description\": \"Permitir que a mesma porta apareça várias vezes\",\n          \"title\": \"Permitir duplicatas\"\n        },\n        \"countDescription\": \"Número de portas aleatórias a serem geradas (1-1.000)\",\n        \"sortResults\": {\n          \"description\": \"Classifique as portas geradas em ordem crescente\",\n          \"title\": \"Classificar resultados\"\n        },\n        \"title\": \"Opções de geração\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Caractere(s) para separar as portas geradas\",\n        \"title\": \"Configurações de saída\"\n      },\n      \"range\": {\n        \"custom\": \"Faixa personalizada\",\n        \"dynamic\": \"Portas Dinâmicas (49152-65535)\",\n        \"maxPortDescription\": \"Número máximo de portas (1-65535)\",\n        \"minPortDescription\": \"Número mínimo de porta (1-65535)\",\n        \"registered\": \"Portos Registrados (1024-49151)\",\n        \"title\": \"Configurações de intervalo de portas\",\n        \"wellKnown\": \"Portos Conhecidos (1-1023)\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Contar\",\n      \"hasDuplicates\": \"Contém duplicatas\",\n      \"isSorted\": \"Classificado\",\n      \"portDetails\": \"Detalhes do porto\",\n      \"range\": \"Port Range\",\n      \"title\": \"Portas aleatórias geradas\"\n    },\n    \"shortDescription\": \"Gerar portas de rede aleatórias\",\n    \"title\": \"Gerador de portas aleatórias\"\n  },\n  \"slackline\": {\n    \"description\": \"Calcula a tensão em uma slackline\",\n    \"longDescription\": \"Esta calculadora assume uma carga no centro da corda\",\n    \"shortDescription\": \"Calcule a tensão aproximada de um slackline ou varal. Não confie apenas nisso para sua segurança.\",\n    \"title\": \"Tensão da Slackline\"\n  },\n  \"sphereArea\": {\n    \"description\": \"Área de uma esfera\",\n    \"longDescription\": \"Esta calculadora determina a área da superfície de uma esfera usando a fórmula A = 4πr². Você pode inserir o raio para encontrar a área da superfície ou inseri-la para calcular o raio necessário. Esta ferramenta é útil para estudantes de geometria, engenheiros que trabalham com objetos esféricos e qualquer pessoa que precise realizar cálculos envolvendo superfícies esféricas.\",\n    \"shortDescription\": \"Calcular a área da superfície de uma esfera com base em seu raio\",\n    \"title\": \"Área de uma esfera\"\n  },\n  \"sphereVolume\": {\n    \"description\": \"Volume de uma esfera\",\n    \"longDescription\": \"Esta calculadora calcula o volume de uma esfera usando a fórmula V = (4/3)πr³. Você pode inserir o raio ou o diâmetro para encontrar o volume ou inserir o volume para determinar o raio necessário. A ferramenta é valiosa para estudantes, engenheiros e profissionais que trabalham com objetos esféricos em áreas como física, engenharia e manufatura.\",\n    \"shortDescription\": \"Calcular o volume de uma esfera usando raio ou diâmetro\",\n    \"title\": \"Volume de uma esfera\"\n  },\n  \"sum\": {\n    \"description\": \"Calcule a soma de uma lista de números. Insira os números separados por vírgulas ou quebras de linha para obter a soma total.\",\n    \"example1Description\": \"Neste exemplo, calculamos a soma de dez números inteiros positivos. Esses números inteiros são listados em uma coluna e sua soma total é igual a 19.494.\",\n    \"example1Title\": \"Soma de Dez Números Positivos\",\n    \"example2Description\": \"Este exemplo inverte uma coluna de vinte substantivos trissílabos e exibe todas as palavras de baixo para cima. Para separar os itens da lista, ele usa o caractere \\\\n como separador de itens de entrada, o que significa que cada item está em sua própria linha.\",\n    \"example2Title\": \"Contagem de árvores no parque\",\n    \"example3Description\": \"Neste exemplo, somamos noventa valores diferentes – números positivos, números negativos, inteiros e frações decimais. Definimos uma vírgula como separador de entrada e, após somar todos eles, obtemos 0 como saída.\",\n    \"example3Title\": \"Soma de números inteiros e decimais\",\n    \"example4Description\": \"Neste exemplo, calculamos a soma de todos os dez dígitos e ativamos a opção \\\"Imprimir Soma Corrente\\\". Obtemos os valores intermediários da soma no processo de adição. Assim, temos a seguinte sequência na saída: 0, 1 (0 + 1), 3 (0 + 1 + 2), 6 (0 + 1 + 2 + 3), 10 (0 + 1 + 2 + 3 + 4) e assim por diante.\",\n    \"example4Title\": \"Soma de números em execução\",\n    \"extractionTypes\": {\n      \"delimiter\": {\n        \"description\": \"Personalize o separador de números aqui. (Por padrão, uma quebra de linha.)\",\n        \"title\": \"Delimitador de Número\"\n      },\n      \"smart\": {\n        \"description\": \"Detectar automaticamente números na entrada.\",\n        \"title\": \"Soma Inteligente\"\n      }\n    },\n    \"inputTitle\": \"Entrada\",\n    \"numberExtraction\": \"Extração de Números\",\n    \"printRunningSum\": \"Imprimir soma corrente\",\n    \"printRunningSumDescription\": \"Exiba a soma conforme ela é calculada passo a passo.\",\n    \"resultTitle\": \"Total\",\n    \"runningSum\": \"Soma Corrente\",\n    \"shortDescription\": \"Calcular soma de números\",\n    \"title\": \"Soma\",\n    \"toolInfo\": {\n      \"description\": \"Este é um utilitário online baseado em navegador para calcular a soma de vários números. Você pode inserir os números separados por vírgula, espaço ou qualquer outro caractere, incluindo a quebra de linha. Você também pode simplesmente colar um fragmento de dados textuais que contenha valores numéricos que você deseja somar, e o utilitário os extrairá e calculará a soma.\",\n      \"title\": \"O que é uma calculadora de soma numérica?\"\n    }\n  },\n  \"voltageDropInWire\": {\n    \"description\": \"Calcula a tensão de ida e volta e a perda de potência em um cabo de 2 condutores\",\n    \"longDescription\": \"Esta calculadora ajuda a determinar a queda de tensão e a perda de potência em um cabo elétrico de dois condutores. Ela leva em consideração o comprimento do cabo, a bitola do fio (área da seção transversal), a resistividade do material e o fluxo de corrente. A ferramenta calcula a queda de tensão de ida e volta, a resistência total do cabo e a potência dissipada na forma de calor. Isso é particularmente útil para engenheiros elétricos, eletricistas e amadores ao projetar sistemas elétricos para garantir que os níveis de tensão permaneçam dentro dos limites aceitáveis na carga.\",\n    \"shortDescription\": \"Calcular queda de tensão e perda de potência em cabos elétricos com base no comprimento, material e corrente\",\n    \"title\": \"Queda de tensão de ida e volta no cabo\"\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/pdf.json",
    "content": "{\n  \"compressPdf\": {\n    \"compressedFileSize\": \"Tamanho do arquivo compactado\",\n    \"compressingPdf\": \"Compactando PDF...\",\n    \"compressionLevel\": \"Nível de compressão\",\n    \"compressionSettings\": \"Configurações de compressão\",\n    \"description\": \"Reduza o tamanho do arquivo PDF mantendo a qualidade usando Ghostscript\",\n    \"errorCompressingPdf\": \"Falha ao compactar PDF: {{error}}\",\n    \"errorReadingPdf\": \"Falha ao ler o arquivo PDF. Certifique-se de que é um PDF válido.\",\n    \"fileSize\": \"Tamanho original do arquivo\",\n    \"highCompression\": \"Alta compressão\",\n    \"highCompressionDescription\": \"Redução máxima do tamanho do arquivo com alguma perda de qualidade\",\n    \"inputTitle\": \"PDF de entrada\",\n    \"longDescription\": \"Compacte arquivos PDF com segurança no seu navegador usando o Ghostscript. Seus arquivos nunca saem do seu dispositivo, garantindo total privacidade e reduzindo o tamanho dos arquivos para compartilhamento por e-mail, upload para sites ou economia de espaço de armazenamento. Desenvolvido com a tecnologia WebAssembly.\",\n    \"lowCompression\": \"Baixa compressão\",\n    \"lowCompressionDescription\": \"Reduza ligeiramente o tamanho do arquivo com perda mínima de qualidade\",\n    \"mediumCompression\": \"Compressão média\",\n    \"mediumCompressionDescription\": \"Equilíbrio entre tamanho e qualidade do arquivo\",\n    \"pages\": \"Número de páginas\",\n    \"resultTitle\": \"PDF compactado\",\n    \"shortDescription\": \"Compacte arquivos PDF com segurança no seu navegador\",\n    \"title\": \"Comprimir PDF\"\n  },\n  \"editor\": {\n    \"description\": \"Editor de PDF avançado com recursos de anotação, preenchimento de formulários, destaque e exportação. Edite seus PDFs diretamente no navegador com ferramentas de nível profissional, incluindo inserção de texto, desenho, destaque, assinatura e preenchimento de formulários.\",\n    \"shortDescription\": \"Edite PDFs com ferramentas avançadas de anotação, assinatura e edição\",\n    \"title\": \"Editor de PDF\"\n  },\n  \"merge\": {\n    \"inputTitle\": \"PDF de entrada\",\n    \"loadingText\": \"Extraindo páginas\",\n    \"resultTitle\": \"Saída PDF mesclada\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite mesclar vários arquivos PDF em um único documento. Para usá-la, basta carregar os arquivos PDF que deseja mesclar. A ferramenta então combinará todas as páginas dos arquivos de entrada em um único documento PDF.\",\n      \"title\": \"Como usar a ferramenta Mesclar PDF?\"\n    }\n  },\n  \"mergePdf\": {\n    \"description\": \"Combine vários arquivos PDF em um único documento.\",\n    \"inputTitle\": \"PDFs de entrada\",\n    \"mergingPdfs\": \"Mesclando PDFs\",\n    \"pdfOptions\": \"Opções de PDF\",\n    \"resultTitle\": \"PDF mesclado\",\n    \"shortDescription\": \"Mesclar vários arquivos PDF em um único documento\",\n    \"sortByFileName\": \"Classificar por nome de arquivo\",\n    \"sortByFileNameDescription\": \"Classificar PDFs em ordem alfabética por nome de arquivo\",\n    \"sortByUploadOrder\": \"Classificar por ordem de upload\",\n    \"sortByUploadOrderDescription\": \"Mantenha os PDFs na ordem em que foram enviados\",\n    \"title\": \"Mesclar PDF\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite combinar vários arquivos PDF em um único documento. Você pode escolher como classificar os PDFs e a ferramenta os mesclará na ordem especificada.\",\n      \"title\": \"Mesclar arquivos PDF\"\n    }\n  },\n  \"pdfToEpub\": {\n    \"description\": \"Transforme documentos PDF em arquivos EPUB para melhor compatibilidade com leitores eletrônicos.\",\n    \"shortDescription\": \"Converter arquivos PDF para o formato EPUB\",\n    \"title\": \"PDF para EPUB\"\n  },\n  \"pdfToPng\": {\n    \"description\": \"Transforme documentos PDF em painéis PNG.\",\n    \"longDescription\": \"Carregue um PDF e converta cada página em uma imagem PNG de alta qualidade diretamente no seu navegador. Esta ferramenta é ideal para extrair conteúdo visual ou compartilhar páginas individuais. Nenhum dado é carregado — tudo é executado localmente.\",\n    \"shortDescription\": \"Converter PDF em imagens PNG\",\n    \"title\": \"PDF para PNG\"\n  },\n  \"convertToPdf\": {\n    \"title\": \"Imagens para PDF\",\n    \"description\": \"Converter vários formatos de imagem (PNG, GIF, JPG, TIF, PSD, SVG, WEBP, HEIC, RAW) em PDF, com opções para redimensionar a imagem e escolher a orientação da página.\",\n    \"shortDescription\": \"Converter imagens em PDF com controle de escala e orientação\"\n  },\n  \"protectPdf\": {\n    \"description\": \"Adicione proteção por senha aos seus arquivos PDF com segurança no seu navegador\",\n    \"shortDescription\": \"Proteja arquivos PDF com senha com segurança\",\n    \"title\": \"Proteger PDF\"\n  },\n  \"rotatePdf\": {\n    \"allPagesWillBeRotated\": \"Todos {{count}} as páginas serão giradas\",\n    \"angleOptions\": {\n      \"clockwise90\": \"90° no sentido horário\",\n      \"counterClockwise270\": \"270° (90° sentido anti-horário)\",\n      \"upsideDown180\": \"180° (de cabeça para baixo)\"\n    },\n    \"applyToAllPages\": \"Aplicar a todas as páginas\",\n    \"description\": \"Girar páginas em um documento PDF.\",\n    \"inputTitle\": \"PDF de entrada\",\n    \"longDescription\": \"Altere a orientação das páginas do PDF girando-as 90, 180 ou 270 graus. Útil para corrigir documentos digitalizados incorretamente ou preparar PDFs para impressão.\",\n    \"pageRangesDescription\": \"Insira números de página ou intervalos separados por vírgulas (por exemplo, 1,3,5-7)\",\n    \"pageRangesPlaceholder\": \"por exemplo, 1,5-8\",\n    \"pagesWillBeRotated\": \"{{count}} página{{count !== 1 ? 's' : ''}} será girado\",\n    \"pdfPageCount\": \"PDF tem {{count}} página{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"PDF girado\",\n    \"rotatingPages\": \"Páginas rotativas\",\n    \"rotationAngle\": \"Ângulo de rotação\",\n    \"rotationSettings\": \"Configurações de rotação\",\n    \"shortDescription\": \"Girar páginas em um documento PDF\",\n    \"title\": \"Girar PDF\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite girar páginas em um documento PDF. Você pode girar todas as páginas ou especificar páginas individuais para girar. Escolha um ângulo de rotação: 90° no sentido horário, 180° (de cabeça para baixo) ou 270° (90° no sentido anti-horário). Para girar páginas específicas, desmarque \\\"Aplicar a todas as páginas\\\" e insira os números das páginas ou intervalos separados por vírgulas (por exemplo, 1, 3, 5-7).\",\n      \"title\": \"Como usar a ferramenta Girar PDF\"\n    }\n  },\n  \"splitPdf\": {\n    \"description\": \"Extraia páginas específicas de um documento PDF.\",\n    \"extractingPages\": \"Extraindo páginas\",\n    \"inputTitle\": \"PDF de entrada\",\n    \"pageExtractionPreview\": \"{{count}} página{{count !== 1 ? 's' : ''}} será extraído\",\n    \"pageRangesDescription\": \"Insira números de página ou intervalos separados por vírgulas (por exemplo, 1,3,5-7)\",\n    \"pageRangesPlaceholder\": \"por exemplo, 1,5-8\",\n    \"pageSelection\": \"Seleção de página\",\n    \"pdfPageCount\": \"PDF tem {{count}} página{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"PDF extraído\",\n    \"shortDescription\": \"Extrair páginas específicas de um arquivo PDF\",\n    \"title\": \"Dividir PDF\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite extrair páginas específicas de um documento PDF. Você pode especificar páginas individuais ou intervalos de páginas para extrair.\",\n      \"title\": \"Dividir PDF\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/string.json",
    "content": "{\n  \"base64\": {\n    \"decode\": \"Decodificação Base64\",\n    \"description\": \"Codifique ou decodifique texto usando codificação Base64.\",\n    \"encode\": \"Codificação Base64\",\n    \"inputTitle\": \"Dados de entrada\",\n    \"optionsTitle\": \"Opções Base64\",\n    \"resultTitle\": \"Resultado\",\n    \"shortDescription\": \"Codifique ou decodifique dados usando Base64.\",\n    \"title\": \"Codificador/Decodificador Base64\",\n    \"toolInfo\": {\n      \"description\": \"Base64 é um esquema de codificação que representa dados em um formato de string ASCII, traduzindo-os para uma representação radix-64. Embora possa ser usado para codificar strings, é comumente usado para codificar dados binários para transmissão em mídias projetadas para lidar com dados textuais.\",\n      \"title\": \"O que é Base64?\"\n    }\n  },\n  \"censor\": {\n    \"description\": \"Utilitário para censurar palavras em texto. Carregue seu texto no formulário de entrada à esquerda, especifique todos os palavrões nas opções e você obterá instantaneamente o texto censurado na área de saída.\\\", longDescription: \\\"Com esta ferramenta online, você pode censurar certas palavras em qualquer texto. Você pode especificar uma lista de palavras indesejadas (como palavrões ou palavras secretas) e o programa as substituirá por palavras alternativas e criará um texto seguro para leitura. As palavras podem ser especificadas em um campo de texto de várias linhas nas opções, inserindo uma palavra por linha.\\\", keywords: ['text', 'censor', 'words', 'characters'], component: lazy(() => import('./index')), i18n: { name: 'string:censor.title', description: 'string:censor.description\",\n    \"shortDescription\": \"Mascare rapidamente palavras ruins ou substitua-as por palavras alternativas.\",\n    \"title\": \"Censor de texto\"\n  },\n  \"createPalindrome\": {\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para criar palíndromos a partir de qualquer texto. Insira texto e transforme-o instantaneamente em um palíndromo com a mesma leitura de trás para frente. Perfeito para jogos de palavras, criação de padrões de texto simétricos ou exploração de curiosidades linguísticas.\",\n    \"shortDescription\": \"Crie um texto que seja lido da mesma forma para frente e para trás\",\n    \"title\": \"Criar palíndromo\"\n  },\n  \"extractSubstring\": {\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para extrair substrings de texto. Insira seu texto e especifique as posições inicial e final para extrair a parte desejada. Perfeito para processamento de dados, análise de texto ou extração de conteúdo específico de blocos de texto maiores.\",\n    \"shortDescription\": \"Extrair uma parte do texto entre posições especificadas\",\n    \"title\": \"Extrair substring\"\n  },\n  \"hiddenCharacterDetector\": {\n    \"analysisOptions\": \"Opções de análise\",\n    \"category\": \"Categoria\",\n    \"description\": \"Detecte caracteres Unicode ocultos, especialmente caracteres RTL Override que podem ser usados em ataques.\",\n    \"foundChars\": \"Encontrado {{count}} personagem(ns) oculto(s):\",\n    \"inputPlaceholder\": \"Digite o texto para verificar se há caracteres ocultos...\",\n    \"inputTitle\": \"Texto para Analisar\",\n    \"invisibleChar\": \"Personagem Invisível\",\n    \"invisibleFound\": \"Personagens invisíveis encontrados\",\n    \"longDescription\": \"Esta ferramenta ajuda a detectar caracteres Unicode ocultos em textos, especialmente caracteres de substituição da direita para a esquerda (RTL), que podem ser usados em ataques. Ela pode identificar caracteres invisíveis, caracteres de largura zero e outras sequências Unicode potencialmente maliciosas que podem estar ocultas em textos aparentemente inofensivos.\",\n    \"noHiddenChars\": \"Nenhum caractere oculto detectado no texto.\",\n    \"optionsDescription\": \"Configure quais tipos de caracteres ocultos detectar e como exibir os resultados.\",\n    \"position\": \"Posição\",\n    \"rtlAlert\": \"⚠️ Caracteres de substituição RTL detectados! Este texto pode conter caracteres ocultos maliciosos.\",\n    \"rtlFound\": \"Substituição RTL encontrada\",\n    \"rtlOverride\": \"Caractere de substituição RTL\",\n    \"rtlWarning\": \"AVISO: Caracteres de substituição RTL detectados! Isso pode ser usado em ataques.\",\n    \"shortDescription\": \"Encontre caracteres Unicode ocultos no texto\",\n    \"summary\": \"Resumo da Análise\",\n    \"title\": \"Detector de caracteres ocultos\",\n    \"totalChars\": \"Total de caracteres ocultos: {{count}}\",\n    \"unicode\": \"Unicode\",\n    \"zeroWidthChar\": \"Caractere de largura zero\",\n    \"zeroWidthFound\": \"Caracteres de largura zero encontrados\"\n  },\n  \"join\": {\n    \"blankLinesAndTrailingSpaces\": \"Linhas em branco e espaços finais\",\n    \"deleteBlankDescription\": \"Exclua linhas que não tenham símbolos de texto.\",\n    \"deleteBlankTitle\": \"Excluir linhas em branco\",\n    \"deleteTrailingDescription\": \"Remova espaços e tabulações no final das linhas.\",\n    \"deleteTrailingTitle\": \"Excluir espaços finais\",\n    \"description\": \"Una partes de texto com separadores personalizáveis.\",\n    \"inputTitle\": \"Pedaços de texto\",\n    \"joinCharacterDescription\": \"Símbolo que conecta partes quebradas de texto. (Espaço por padrão.)\",\n    \"joinCharacterPlaceholder\": \"Junte-se ao personagem\",\n    \"resultTitle\": \"Texto unido\",\n    \"shortDescription\": \"Unir elementos de texto com um separador especificado\",\n    \"textMergedOptions\": \"Opções de mesclagem de texto\",\n    \"title\": \"Junte-se ao texto\",\n    \"toolInfo\": {\n      \"description\": \"Com esta ferramenta, você pode unir partes do texto. Ela pega uma lista de valores de texto, separados por quebras de linha, e os mescla. Você pode definir o caractere que será colocado entre as partes do texto combinado. Além disso, você pode ignorar todas as linhas vazias e remover espaços e tabulações no final de todas as linhas. Textabulous!\",\n      \"title\": \"O que é um unidor de texto?\"\n    }\n  },\n  \"palindrome\": {\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para verificar se um texto é um palíndromo. Verifique instantaneamente se o seu texto é lido da mesma forma, de trás para frente. Perfeito para quebra-cabeças de palavras, análise linguística ou validação de padrões de texto simétricos. Suporta vários delimitadores e detecção de palíndromos de várias palavras.\",\n    \"shortDescription\": \"Verifique se o texto é lido da mesma forma para frente e para trás\",\n    \"title\": \"Palíndromo\"\n  },\n  \"passwordGenerator\": {\n    \"avoidAmbiguous\": \"Evite caracteres ambíguos (i, I, l, 0, O)\",\n    \"description\": \"Gere senhas aleatórias e seguras com comprimento e tipos de caracteres personalizáveis. Escolha entre letras minúsculas, maiúsculas, números e caracteres especiais. Opção para evitar caracteres ambíguos para melhor legibilidade.\",\n    \"includeLowercase\": \"Incluir letras minúsculas (a-z)\",\n    \"includeNumbers\": \"Incluir números (0-9)\",\n    \"includeSymbols\": \"Incluir caracteres especiais\",\n    \"includeUppercase\": \"Incluir letras maiúsculas (A-Z)\",\n    \"lengthDesc\": \"Comprimento da senha\",\n    \"lengthPlaceholder\": \"por exemplo 12\",\n    \"optionsTitle\": \"Opções de senha\",\n    \"resultTitle\": \"Senha gerada\",\n    \"shortDescription\": \"Gere senhas aleatórias seguras com opções personalizadas\",\n    \"title\": \"Gerador de senhas\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta gera senhas aleatórias e seguras com base nos critérios selecionados. Você pode personalizar o comprimento, incluir ou excluir diferentes tipos de caracteres e evitar caracteres ambíguos para melhor legibilidade. Perfeito para criar senhas fortes para contas, aplicativos ou qualquer necessidade de segurança.\",\n      \"title\": \"Sobre o gerador de senhas\"\n    }\n  },\n  \"quote\": {\n    \"allowDoubleQuotation\": \"Permitir aspas duplas\",\n    \"description\": \"Adicione aspas ao redor do texto com opções personalizáveis.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"leftQuoteDescription\": \"Caractere(s) de aspas à esquerda\",\n    \"processAsMultiLine\": \"Processar como texto multilinha\",\n    \"quoteEmptyLines\": \"Citar linhas vazias\",\n    \"quoteOptions\": \"Opções de cotação\",\n    \"resultTitle\": \"Texto citado\",\n    \"rightQuoteDescription\": \"Caractere(s) de aspas corretas\",\n    \"shortDescription\": \"Adicione aspas ao redor do texto com vários estilos\",\n    \"title\": \"Citador de texto\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite adicionar aspas ao redor do texto. Você pode escolher diferentes caracteres de aspas, manipular texto com várias linhas e controlar como as linhas vazias são processadas. É útil para preparar texto para programação, formatar dados ou criar texto estilizado.\",\n      \"title\": \"Citador de texto\"\n    }\n  },\n  \"randomizeCase\": {\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para randomizar letras maiúsculas e minúsculas em textos. Insira seu texto e transforme-o instantaneamente com letras maiúsculas e minúsculas aleatórias. Perfeito para criar efeitos de texto exclusivos, testar a diferenciação entre maiúsculas e minúsculas ou gerar padrões de texto variados.\",\n    \"shortDescription\": \"Randomize a caixa das letras no texto\",\n    \"title\": \"Randomizar caso\"\n  },\n  \"removeDuplicateLines\": {\n    \"description\": \"Carregue seu texto no formulário de entrada à esquerda e você obterá instantaneamente texto sem linhas duplicadas na área de saída. Poderoso, gratuito e rápido. Carregue linhas de texto – obtenha linhas de texto exclusivas.\",\n    \"shortDescription\": \"Exclua rapidamente todas as linhas repetidas do texto\",\n    \"title\": \"Remover linhas duplicadas\"\n  },\n  \"repeat\": {\n    \"delimiterDescription\": \"Delimitador para cópias de saída.\",\n    \"delimiterPlaceholder\": \"Delimitador\",\n    \"description\": \"Repita o texto várias vezes com separadores personalizáveis.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"numberPlaceholder\": \"Número\",\n    \"repeatAmountDescription\": \"Número de repetições.\",\n    \"repetitionsDelimiter\": \"Delimitador de Repetições\",\n    \"resultTitle\": \"Texto repetido\",\n    \"shortDescription\": \"Repita o texto várias vezes\",\n    \"textRepetitions\": \"Repetições de texto\",\n    \"title\": \"Repetir texto\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite que você repita um determinado texto várias vezes com um separador opcional.\",\n      \"title\": \"Repetir texto\"\n    }\n  },\n  \"reverse\": {\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para inverter texto. Insira qualquer texto e inverta-o instantaneamente, caractere por caractere. Perfeito para criar texto espelhado, analisar palíndromos ou brincar com padrões de texto. Preserva espaços e caracteres especiais durante a inversão.\",\n    \"inputTitle\": \"Texto para reverter\",\n    \"processMultiLine\": \"Processar texto multilinha\",\n    \"processMultiLineDescription\": \"Cada linha será revertida independentemente\",\n    \"resultTitle\": \"Texto invertido\",\n    \"reversalOptions\": \"Opções de reversão\",\n    \"shortDescription\": \"Inverter qualquer texto caractere por caractere\",\n    \"skipEmptyLines\": \"Pular linhas vazias\",\n    \"skipEmptyLinesDescription\": \"As linhas vazias serão removidas da saída\",\n    \"title\": \"Reverter\",\n    \"trimWhitespace\": \"Aparar espaços em branco\",\n    \"trimWhitespaceDescription\": \"Remova os espaços em branco à esquerda e à direita de cada linha\"\n  },\n  \"rot13\": {\n    \"description\": \"Codifique ou decodifique texto usando a cifra ROT13.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"resultTitle\": \"Resultado ROT13\",\n    \"shortDescription\": \"Codifique ou decodifique texto usando a cifra ROT13.\",\n    \"title\": \"Codificador/Decodificador ROT13\",\n    \"toolInfo\": {\n      \"description\": \"ROT13 (rotação de 13 posições) é uma cifra simples de substituição de letras que substitui uma letra pela 13ª letra seguinte no alfabeto. ROT13 é um caso especial da cifra de César, desenvolvida na Roma Antiga. Como o alfabeto inglês possui 26 letras, ROT13 é seu próprio inverso; ou seja, para desfazer a ROT13, o mesmo algoritmo é aplicado, permitindo que a mesma ação seja usada para codificação e decodificação.\",\n      \"title\": \"O que é ROT13?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"Girar caracteres no texto por posições especificadas.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"processAsMultiLine\": \"Processar como texto multilinha (girar cada linha separadamente)\",\n    \"resultTitle\": \"Texto girado\",\n    \"rotateLeft\": \"Girar para a esquerda\",\n    \"rotateRight\": \"Girar para a direita\",\n    \"rotationOptions\": \"Opções de rotação\",\n    \"shortDescription\": \"Deslocar caracteres no texto por posição.\",\n    \"stepDescription\": \"Número de posições para girar\",\n    \"title\": \"Girar texto\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite girar caracteres em uma string por um número específico de posições. Você pode girar para a esquerda ou para a direita e processar texto com várias linhas girando cada linha separadamente. A rotação de strings é útil para transformações simples de texto, criação de padrões ou implementação de técnicas básicas de criptografia.\",\n      \"title\": \"Rotação de cordas\"\n    }\n  },\n  \"split\": {\n    \"charAfterChunkDescription\": \"Caractere após cada pedaço\",\n    \"charBeforeChunkDescription\": \"Caractere antes de cada pedaço\",\n    \"chunksDescription\": \"Número de pedaços de igual\\ncomprimento na saída.\",\n    \"chunksTitle\": \"Use vários pedaços\",\n    \"description\": \"O utilitário baseado em navegador mais simples do mundo para dividir texto. Insira seu texto e especifique um separador para dividi-lo em várias partes. Perfeito para processamento de dados, manipulação de texto ou extração de conteúdo específico de blocos de texto maiores.\",\n    \"lengthDescription\": \"Número de símbolos que serão colocados em cada bloco de saída.\",\n    \"lengthTitle\": \"Use comprimento para divisão\",\n    \"outputSeparatorDescription\": \"Caractere que será colocado entre os blocos divididos.\\n(Por padrão, é a quebra de linha \\\"\\\\n\\\".)\",\n    \"outputSeparatorOptions\": \"Opções de separador de saída\",\n    \"regexDescription\": \"Expressão regular que será usada para dividir o texto em partes.\\n(Vários espaços por padrão.)\",\n    \"regexTitle\": \"Use um Regex para Divisão\",\n    \"resultTitle\": \"Pedaços de texto\",\n    \"shortDescription\": \"Dividir o texto em várias partes usando um separador\",\n    \"splitSeparatorOptions\": \"Opções de separador de divisão\",\n    \"symbolDescription\": \"Caractere que será usado para dividir o texto em partes.\\n(Espaço por padrão.)\",\n    \"symbolTitle\": \"Use um símbolo para dividir\",\n    \"title\": \"Dividir\"\n  },\n  \"statistic\": {\n    \"characterFrequencyAnalysis\": \"Análise de Frequência de Caracteres\",\n    \"characterFrequencyAnalysisDescription\": \"Conte com que frequência cada caractere aparece no texto\",\n    \"delimitersOptions\": \"Opções de delimitadores\",\n    \"description\": \"Analise texto e gere estatísticas abrangentes.\",\n    \"includeEmptyLines\": \"Incluir linhas vazias\",\n    \"includeEmptyLinesDescription\": \"Incluir linhas em branco ao contar linhas\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"resultTitle\": \"Estatísticas de texto\",\n    \"sentenceDelimitersDescription\": \"Insira caracteres personalizados usados para delimitar frases no seu idioma (separados por vírgula) ou deixe em branco para o padrão.\",\n    \"sentenceDelimitersPlaceholder\": \"por exemplo ., !, ?, ...\",\n    \"shortDescription\": \"Obtenha estatísticas sobre seu texto\",\n    \"statisticsOptions\": \"Opções de estatísticas\",\n    \"title\": \"Estatísticas de texto\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite que você analise texto e gere estatísticas abrangentes, incluindo contagem de caracteres, contagem de palavras, contagem de linhas e análise de frequência de caracteres e palavras.\",\n      \"title\": \"O que é um {{title}}?\"\n    },\n    \"wordDelimitersDescription\": \"Insira uma Regex personalizada para contar palavras ou deixe em branco para o padrão.\",\n    \"wordDelimitersPlaceholder\": \"por exemplo. \\\\s.,;:!?\\\"«»()…\",\n    \"wordFrequencyAnalysis\": \"Análise de frequência de palavras\",\n    \"wordFrequencyAnalysisDescription\": \"Conte com que frequência cada palavra aparece no texto\"\n  },\n  \"textReplacer\": {\n    \"description\": \"Substitua padrões de texto por novo conteúdo.\",\n    \"findPatternInText\": \"Encontre este padrão no texto\",\n    \"findPatternUsingRegexp\": \"Encontre um padrão usando uma RegExp\",\n    \"inputTitle\": \"Texto para substituir\",\n    \"newTextPlaceholder\": \"Novo texto\",\n    \"regexpDescription\": \"Insira a expressão regular que você deseja substituir.\",\n    \"replacePatternDescription\": \"Digite o padrão a ser usado para substituição.\",\n    \"replaceText\": \"Substituir texto\",\n    \"resultTitle\": \"Texto com substituições\",\n    \"searchPatternDescription\": \"Digite o padrão de texto que você deseja substituir.\",\n    \"searchText\": \"Pesquisar texto\",\n    \"shortDescription\": \"Substitua rapidamente o texto em seu conteúdo\",\n    \"title\": \"Substituidor de texto\",\n    \"toolInfo\": {\n      \"description\": \"Substitua facilmente textos específicos em seu conteúdo com esta ferramenta simples para navegador. Basta inserir o texto, definir o texto que deseja substituir e o valor de substituição e obter a versão atualizada instantaneamente.\",\n      \"title\": \"Substituidor de texto\"\n    }\n  },\n  \"toMorse\": {\n    \"dashSymbolDescription\": \"Símbolo que corresponderá ao travessão no código Morse.\",\n    \"description\": \"Converta texto em código Morse.\",\n    \"dotSymbolDescription\": \"Símbolo que corresponderá ao ponto no código Morse.\",\n    \"longSignal\": \"Sinal longo\",\n    \"resultTitle\": \"código Morse\",\n    \"shortDescription\": \"Codifique rapidamente o texto para Morse\",\n    \"shortSignal\": \"Sinal curto\",\n    \"title\": \"String para Morse\"\n  },\n  \"truncate\": {\n    \"addTruncationIndicator\": \"Adicionar Indicador de Truncamento\",\n    \"charactersPlaceholder\": \"Personagens\",\n    \"description\": \"Encurte o texto para um comprimento especificado.\",\n    \"indicatorDescription\": \"Caracteres a serem adicionados ao final (ou início) do texto. Observação: eles contam para o comprimento.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"leftSideDescription\": \"Remova caracteres do início do texto.\",\n    \"leftSideTruncation\": \"Truncamento do lado esquerdo\",\n    \"lengthAndLines\": \"Comprimento e Linhas\",\n    \"lineByLineDescription\": \"Trunque cada linha separadamente.\",\n    \"lineByLineTruncating\": \"Truncamento linha por linha\",\n    \"maxLengthDescription\": \"Número de caracteres a deixar no texto.\",\n    \"numberPlaceholder\": \"Número\",\n    \"resultTitle\": \"Texto truncado\",\n    \"rightSideDescription\": \"Remova caracteres do final do texto.\",\n    \"rightSideTruncation\": \"Truncamento do lado direito\",\n    \"shortDescription\": \"Truncar texto para um comprimento especificado\",\n    \"suffixAndAffix\": \"Sufixo e Afixo\",\n    \"title\": \"Truncar texto\",\n    \"toolInfo\": {\n      \"description\": \"Carregue seu texto no formulário de entrada à esquerda e você obterá automaticamente o texto truncado à direita.\",\n      \"title\": \"Truncar texto\"\n    },\n    \"truncationSide\": \"Lado de truncamento\"\n  },\n  \"uppercase\": {\n    \"description\": \"Converta texto em letras maiúsculas.\",\n    \"inputTitle\": \"Texto de entrada\",\n    \"resultTitle\": \"Texto em maiúsculas\",\n    \"shortDescription\": \"Converter texto em letras maiúsculas\",\n    \"title\": \"Converter para maiúsculas\"\n  },\n  \"urlDecode\": {\n    \"inputTitle\": \"String de entrada (com escape de URL)\",\n    \"resultTitle\": \"Cadeia de saída\",\n    \"toolInfo\": {\n      \"description\": \"Carregue sua string e ela receberá automaticamente o URL sem escape.\",\n      \"longDescription\": \"Esta ferramenta decodifica por URL uma string previamente codificada por URL. A decodificação por URL é a operação inversa da codificação por URL. Todos os caracteres codificados em porcentagem são decodificados para caracteres que você possa entender. Alguns dos valores codificados em porcentagem mais conhecidos são %20 para um espaço, %3a para dois pontos, %2f para uma barra e %3f para um ponto de interrogação. Os dois dígitos após o sinal de porcentagem são os valores do código char do caractere em hexadecimal.\",\n      \"shortDescription\": \"Desfazer o escape de URL de uma string rapidamente.\",\n      \"title\": \"Decodificador de URL de string\"\n    }\n  },\n  \"urlEncode\": {\n    \"encodingOption\": {\n      \"nonSpecialCharDescription\": \"Se selecionado, todos os caracteres na sequência de entrada serão convertidos para codificação de URL (não apenas especial).\",\n      \"nonSpecialCharPlaceholder\": \"Codificar caracteres não especiais\",\n      \"title\": \"Opções de codificação\"\n    },\n    \"inputTitle\": \"Cadeia de entrada\",\n    \"resultTitle\": \"String com escape de URL\",\n    \"toolInfo\": {\n      \"description\": \"Carregue sua string e ela terá o URL escapado automaticamente.\",\n      \"longDescription\": \"Esta ferramenta codifica uma string por URL. Caracteres especiais de URL são convertidos para codificação de sinal de porcentagem. Essa codificação é chamada de codificação de porcentagem porque o valor numérico de cada caractere é convertido para um sinal de porcentagem seguido por um valor hexadecimal de dois dígitos. Os valores hexadecimais são determinados com base no valor do ponto de código do caractere. Por exemplo, um espaço é convertido para %20, dois-pontos para %3a e uma barra para %2f. Caracteres que não são especiais permanecem inalterados. Caso você também precise converter caracteres não especiais para codificação de porcentagem, adicionamos uma opção extra que permite isso. Selecione a opção encode-non-special-chars para habilitar esse comportamento.\",\n      \"shortDescription\": \"Escapar rapidamente uma string por URL.\",\n      \"title\": \"Codificador de URL de string\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/time.json",
    "content": "{\n  \"checkLeapYears\": {\n    \"description\": \"Verifique se um ano é bissexto e obtenha informações sobre o ano bissexto.\",\n    \"exampleDescription\": \"Uma de nossas amigas nasceu em um ano bissexto, em 29 de fevereiro, e, como consequência, ela faz aniversário apenas uma vez a cada 4 anos. Como nunca conseguimos lembrar exatamente quando é o aniversário dela, estamos usando nosso programa para criar uma lista de lembretes dos próximos anos bissextos. Para criar uma lista dos próximos aniversários dela, carregamos uma sequência de anos de 2025 a 2040 na entrada e obtemos o status de cada ano. Se o programa disser que é um ano bissexto, sabemos que seremos convidados para uma festa de aniversário no dia 29 de fevereiro.\",\n    \"exampleTitle\": \"Encontre aniversários em 29 de fevereiro\",\n    \"inputTitle\": \"Ano de entrada\",\n    \"resultTitle\": \"Resultado do ano bissexto\",\n    \"shortDescription\": \"Verifique se um ano é bissexto\",\n    \"title\": \"Verifique os anos bissextos\",\n    \"toolInfo\": {\n      \"description\": \"Um ano bissexto é um ano que contém um dia adicional (29 de fevereiro) para manter o ano civil sincronizado com o ano astronômico. Anos bissextos ocorrem a cada 4 anos, exceto anos divisíveis por 100, mas não por 400.\",\n      \"title\": \"O que é um ano bissexto?\"\n    }\n  },\n  \"convertDaysToHours\": {\n    \"addHoursName\": \"Adicionar nome das horas\",\n    \"addHoursNameDescription\": \"Adicionar a string horas aos valores de saída\",\n    \"description\": \"Converta dias em horas com opções personalizáveis.\",\n    \"hoursName\": \"Nome das horas\",\n    \"shortDescription\": \"Converter dias em horas\",\n    \"title\": \"Converter dias em horas\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite converter dias em horas. Você pode inserir dias como números ou unidades, e a ferramenta os converterá para horas. Você também pode optar por adicionar o sufixo \\\"horas\\\" aos valores de saída.\",\n      \"title\": \"Converter dias em horas\"\n    }\n  },\n  \"convertHoursToDays\": {\n    \"addDaysName\": \"Adicionar nome dos dias\",\n    \"addDaysNameDescription\": \"Adicione a string days aos valores de saída\",\n    \"daysName\": \"Nome dos dias\",\n    \"description\": \"Converta horas em dias com opções personalizáveis.\",\n    \"shortDescription\": \"Converter horas em dias\",\n    \"title\": \"Converter horas em dias\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite converter horas em dias. Você pode inserir horas como números ou com unidades, e a ferramenta as converterá para dias. Você também pode optar por adicionar o sufixo \\\"dias\\\" aos valores de saída.\",\n      \"title\": \"Converter horas em dias\"\n    }\n  },\n  \"convertSecondsToTime\": {\n    \"addPadding\": \"Adicionar preenchimento\",\n    \"addPaddingDescription\": \"Adicione preenchimento zero às horas, minutos e segundos.\",\n    \"description\": \"Converta os segundos para um formato de hora legível (horas:minutos:segundos). Insira o número de segundos para obter a hora formatada.\",\n    \"shortDescription\": \"Converter segundos para o formato de hora\",\n    \"timePadding\": \"Preenchimento de tempo\",\n    \"title\": \"Converter segundos em tempo\",\n    \"toolInfo\": {\n      \"title\": \"O que é um {{title}}?\"\n    }\n  },\n  \"convertTimeToSeconds\": {\n    \"description\": \"Converter hora formatada (HH:MM:SS) em segundos.\",\n    \"inputTitle\": \"Tempo de entrada\",\n    \"resultTitle\": \"Segundos\",\n    \"shortDescription\": \"Converter formato de hora em segundos\",\n    \"title\": \"Converter tempo em segundos\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite converter sequências de tempo formatadas (HH:MM:SS) em segundos. É útil para calcular durações e intervalos de tempo.\",\n      \"title\": \"Converter tempo em segundos\"\n    }\n  },\n  \"convertUnixToDate\": {\n    \"addUtcLabel\": \"Adicionar sufixo 'UTC'\",\n    \"addUtcLabelDescription\": \"Exibir 'UTC' após a data convertida (somente para o modo UTC)\",\n    \"description\": \"Converta um registro de data e hora Unix em uma data legível por humanos.\",\n    \"outputOptions\": \"Opções de saída\",\n    \"shortDescription\": \"Converter registro de data e hora Unix em data\",\n    \"title\": \"Converter Unix para Data\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta converte um registro de data e hora Unix (em segundos) em um formato de data legível (por exemplo, AAAA-MM-DD HH:MM:SS). Ela suporta saída local e UTC, o que a torna útil para interpretar rapidamente registros de data e hora de logs, APIs ou sistemas que utilizam o horário Unix.\",\n      \"title\": \"Converter Unix para Data\"\n    },\n    \"useLocalTime\": \"Usar hora local\",\n    \"useLocalTimeDescription\": \"Mostrar a data convertida no seu fuso horário local em vez de UTC\",\n    \"withLabel\": \"Opções\"\n  },\n  \"crontabGuru\": {\n    \"description\": \"Gere e compreenda expressões cron. Crie agendamentos cron para tarefas automatizadas e trabalhos do sistema.\",\n    \"shortDescription\": \"Gerar e compreender expressões cron\",\n    \"title\": \"Guru Crontab\"\n  },\n  \"timeBetweenDates\": {\n    \"description\": \"Calcule a diferença de tempo entre duas datas. Obtenha a duração exata em dias, horas, minutos e segundos.\",\n    \"endDate\": \"Data de término\",\n    \"endDateTime\": \"Data e hora de término\",\n    \"endTime\": \"Fim dos tempos\",\n    \"endTimezone\": \"Fim do fuso horário\",\n    \"shortDescription\": \"Calcular o tempo entre duas datas\",\n    \"startDate\": \"Data de início\",\n    \"startDateTime\": \"Data e hora de início\",\n    \"startTime\": \"Hora de início\",\n    \"startTimezone\": \"Fuso horário de início\",\n    \"title\": \"Tempo entre datas\",\n    \"toolInfo\": {\n      \"description\": \"Calcule a diferença horária exata entre duas datas e horas, com suporte para diferentes fusos horários. Esta ferramenta fornece uma análise detalhada da diferença horária em várias unidades (anos, meses, dias, horas, minutos e segundos).\",\n      \"title\": \"Calculadora de tempo entre datas\"\n    }\n  },\n  \"truncateClockTime\": {\n    \"description\": \"Trunque o horário do relógio para remover segundos ou minutos. Arredonde o horário para a hora, minuto ou intervalo personalizado mais próximo.\",\n    \"printDroppedComponents\": \"Imprimir componentes descartados\",\n    \"shortDescription\": \"Truncar o tempo do relógio para a precisão especificada\",\n    \"timePadding\": \"Preenchimento de tempo\",\n    \"title\": \"Truncar o tempo do relógio\",\n    \"toolInfo\": {\n      \"title\": \"O que é um {{title}}?\"\n    },\n    \"truncateMinutesAndSeconds\": \"Truncar minutos e segundos\",\n    \"truncateMinutesAndSecondsDescription\": \"Elimine ambos – os componentes de minutos e segundos de cada relógio.\",\n    \"truncateOnlySeconds\": \"Truncar apenas segundos\",\n    \"truncateOnlySecondsDescription\": \"Retire o componente de segundos de cada hora do relógio.\",\n    \"truncationSide\": \"Lado de truncamento\",\n    \"useZeroPadding\": \"Usar preenchimento zero\",\n    \"zeroPaddingDescription\": \"Faça com que todos os componentes de tempo tenham sempre dois dígitos de largura.\",\n    \"zeroPrintDescription\": \"Exibe as partes descartadas como valores zero \\\"00\\\".\",\n    \"zeroPrintTruncatedParts\": \"Partes truncadas com impressão zero\"\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/translation.json",
    "content": "{\n  \"audio\": {\n    \"changeSpeed\": {\n      \"description\": \"Altere a velocidade de reprodução dos arquivos de áudio. Acelere ou desacelere o áudio, mantendo o tom.\",\n      \"name\": \"Alterar velocidade do áudio\",\n      \"shortDescription\": \"Alterar a velocidade dos arquivos de áudio\"\n    },\n    \"extractAudio\": {\n      \"description\": \"Extraia a faixa de áudio de um arquivo de vídeo e salve-a como um arquivo de áudio separado no formato escolhido (AAC, MP3, WAV).\",\n      \"name\": \"Extrair áudio\",\n      \"shortDescription\": \"Extraia áudio de arquivos de vídeo (MP4, MOV, etc.) para AAC, MP3 ou WAV.\"\n    }\n  },\n  \"baseFileInput\": {\n    \"copyFailed\": \"Falha ao copiar: {{error}}\",\n    \"dropFileHere\": \"Largue o seu {{type}} aqui\",\n    \"fileCopied\": \"Arquivo copiado\",\n    \"selectFileDescription\": \"Clique aqui para selecionar um {{type}} do seu dispositivo, pressione Ctrl+V para usar um {{type}} da sua área de transferência ou arraste e solte um arquivo da área de trabalho\"\n  },\n  \"categories\": {\n    \"audio\": {\n      \"description\": \"Ferramentas para trabalhar com áudio – extrair áudio de vídeo, ajustar a velocidade do áudio, mesclar vários arquivos de áudio e muito mais.\",\n      \"title\": \"Ferramentas de áudio\"\n    },\n    \"csv\": {\n      \"description\": \"Ferramentas para trabalhar com arquivos CSV - converta CSV para diferentes formatos, manipule dados CSV, valide a estrutura CSV e processe arquivos CSV com eficiência.\",\n      \"title\": \"Ferramentas CSV\"\n    },\n    \"gif\": {\n      \"description\": \"Ferramentas para trabalhar com animações GIF – crie GIFs transparentes, extraia quadros de GIF, adicione texto ao GIF, corte, gire, inverta GIFs e muito mais.\",\n      \"title\": \"Ferramentas GIF\"\n    },\n    \"image-generic\": {\n      \"description\": \"Ferramentas para trabalhar com imagens – compactar, redimensionar, cortar, converter para JPG, girar, remover fundo e muito mais.\",\n      \"title\": \"Ferramentas de imagem\"\n    },\n    \"json\": {\n      \"description\": \"Ferramentas para trabalhar com estruturas de dados JSON – embelezar e minimizar objetos JSON, achatar matrizes JSON, transformar valores JSON em strings, analisar dados e muito mais\",\n      \"title\": \"Ferramentas JSON\"\n    },\n    \"list\": {\n      \"description\": \"Ferramentas para trabalhar com listas – classificar, reverter, randomizar listas, encontrar itens de lista exclusivos e duplicados, alterar separadores de itens de lista e muito mais.\",\n      \"title\": \"Ferramentas de lista\"\n    },\n    \"number\": {\n      \"description\": \"Ferramentas para trabalhar com números – gerar sequências numéricas, converter números em palavras e palavras em números, classificar, arredondar, fatorar números e muito mais.\",\n      \"title\": \"Ferramentas numéricas\"\n    },\n    \"pdf\": {\n      \"description\": \"Ferramentas para trabalhar com arquivos PDF - extraia texto de PDFs, converta PDFs para outros formatos, manipule PDFs e muito mais.\",\n      \"title\": \"Ferramentas de PDF\"\n    },\n    \"png\": {\n      \"description\": \"Ferramentas para trabalhar com imagens PNG – converta PNGs em JPGs, crie PNGs transparentes, altere cores de PNG, corte, gire, redimensione PNGs e muito mais.\",\n      \"title\": \"Ferramentas PNG\"\n    },\n    \"seeAll\": \"Ver tudo {{title}}\",\n    \"string\": {\n      \"description\": \"Ferramentas para trabalhar com texto – converter texto em imagens, localizar e substituir texto, dividir texto em fragmentos, unir linhas de texto, repetir texto e muito mais.\",\n      \"title\": \"Ferramentas de texto\"\n    },\n    \"time\": {\n      \"description\": \"Ferramentas para trabalhar com hora e data – calcule diferenças de horário, converta entre fusos horários, formate datas, gere sequências de datas e muito mais.\",\n      \"title\": \"Ferramentas de tempo\"\n    },\n    \"try\": \"Tentar {{title}}\",\n    \"video\": {\n      \"description\": \"Ferramentas para trabalhar com vídeos – extraia quadros de vídeos, crie GIFs de vídeos, converta vídeos para diferentes formatos e muito mais.\",\n      \"title\": \"Ferramentas de vídeo\"\n    },\n    \"xml\": {\n      \"description\": \"Ferramentas para trabalhar com estruturas de dados XML - visualizador, embelezador, validador e muito mais\",\n      \"title\": \"Ferramentas XML\"\n    }\n  },\n  \"csv\": {\n    \"findIncompleteCsvRecords\": {\n      \"description\": \"Basta enviar seu arquivo CSV no formulário abaixo e esta ferramenta verificará automaticamente se nenhuma das linhas ou colunas contém valores ausentes. Nas opções da ferramenta, você pode ajustar o formato do arquivo de entrada (especifique o delimitador, o caractere de aspas e o caractere de comentário). Além disso, você pode ativar a verificação de valores vazios, ignorar linhas vazias e definir um limite para o número de mensagens de erro na saída.\",\n      \"name\": \"Encontre registros CSV incompletos\",\n      \"shortDescription\": \"Encontre rapidamente linhas e colunas em CSV que não tenham valores.\"\n    }\n  },\n  \"hero\": {\n    \"brand\": \"OmniTools\",\n    \"description\": \"Aumente sua produtividade com o OmniTools, o kit de ferramentas definitivo para fazer as coisas rapidamente! Acesse milhares de utilitários fáceis de usar para editar imagens, textos, listas e dados, tudo diretamente do seu navegador.\",\n    \"examples\": {\n      \"calculateNumberSum\": \"Calcular soma numérica\",\n      \"changeGifSpeed\": \"Alterar velocidade do GIF\",\n      \"compressPng\": \"Comprimir PNG\",\n      \"createTransparentImage\": \"Crie uma imagem transparente\",\n      \"prettifyJson\": \"Embeleze JSON\",\n      \"sortList\": \"Classificar uma lista\",\n      \"splitPdf\": \"Dividir PDF\",\n      \"splitText\": \"Dividir um texto\",\n      \"trimVideo\": \"Cortar vídeo\"\n    },\n    \"searchPlaceholder\": \"Pesquisar todas as ferramentas\",\n    \"title\": \"Faça as coisas rapidamente com\"\n  },\n  \"inputFooter\": {\n    \"clear\": \"Claro\",\n    \"copyToClipboard\": \"Copiar para a área de transferência\",\n    \"importFromFile\": \"Importar de arquivo\"\n  },\n  \"list\": {\n    \"group\": {\n      \"description\": \"O utilitário baseado em navegador mais simples do mundo para agrupar itens de lista. Insira sua lista e especifique critérios de agrupamento para organizar os itens em grupos lógicos. Perfeito para categorizar dados, organizar informações ou criar listas estruturadas. Suporta separadores personalizados e diversas opções de agrupamento.\",\n      \"name\": \"Grupo\",\n      \"shortDescription\": \"Agrupar itens da lista por propriedades comuns\"\n    },\n    \"reverse\": {\n      \"description\": \"Este é um aplicativo super simples baseado em navegador que imprime todos os itens da lista em ordem inversa. Os itens de entrada podem ser separados por qualquer símbolo e você também pode alterar o separador dos itens da lista em ordem inversa.\",\n      \"name\": \"Reverter\",\n      \"shortDescription\": \"Inverter uma lista rapidamente\"\n    },\n    \"sort\": {\n      \"description\": \"Este é um aplicativo super simples baseado em navegador que classifica itens em uma lista e os organiza em ordem crescente ou decrescente. Você pode classificar os itens em ordem alfabética, numérica ou por tamanho. Você também pode remover itens duplicados e vazios, bem como cortar itens individuais com espaços em branco. Você pode usar qualquer caractere separador para separar os itens da lista de entrada ou, alternativamente, usar uma expressão regular para separá-los. Além disso, você pode criar um novo delimitador para a lista de saída classificada.\",\n      \"name\": \"Organizar\",\n      \"shortDescription\": \"Classifique uma lista rapidamente\"\n    }\n  },\n  \"navbar\": {\n    \"buyMeACoffee\": \"Compre-me um café\",\n    \"hireMe\": \"Me contrate\",\n    \"home\": \"Lar\",\n    \"tools\": \"Ferramentas\"\n  },\n  \"number\": {\n    \"generate\": {\n      \"description\": \"Calcule rapidamente uma lista de números inteiros no seu navegador. Para obter sua lista, basta especificar o primeiro número inteiro, alterar o valor e a contagem total nas opções abaixo, e este utilitário gerará essa quantidade de números inteiros.\",\n      \"name\": \"Gerar números\",\n      \"shortDescription\": \"Calcule rapidamente uma lista de inteiros no seu navegador\"\n    },\n    \"sum\": {\n      \"description\": \"Este é um aplicativo super simples para navegador que soma números. Os números inseridos podem ser separados por qualquer símbolo e você também pode alterar o separador dos números somados.\",\n      \"name\": \"Somar números\",\n      \"shortDescription\": \"Some rapidamente uma lista de números\"\n    }\n  },\n  \"numericInputWithUnit\": {\n    \"unit\": \"Unidade\"\n  },\n  \"pdf\": {\n    \"compressPdf\": {\n      \"description\": \"Reduza o tamanho do arquivo PDF mantendo a qualidade usando Ghostscript\",\n      \"name\": \"Comprimir PDF\",\n      \"shortDescription\": \"Compacte arquivos PDF com segurança no seu navegador\"\n    },\n    \"mergePdf\": {\n      \"description\": \"Combine vários arquivos PDF em um único documento.\",\n      \"name\": \"Mesclar PDF\",\n      \"shortDescription\": \"Mesclar vários arquivos PDF em um único documento\"\n    },\n    \"pdfToEpub\": {\n      \"description\": \"Transforme documentos PDF em arquivos EPUB para melhor compatibilidade com leitores eletrônicos.\",\n      \"name\": \"PDF para EPUB\",\n      \"shortDescription\": \"Converter arquivos PDF para o formato EPUB\"\n    },\n    \"protectPdf\": {\n      \"description\": \"Adicione proteção por senha aos seus arquivos PDF com segurança no seu navegador\",\n      \"name\": \"Proteger PDF\",\n      \"shortDescription\": \"Proteja arquivos PDF com senha com segurança\"\n    },\n    \"splitPdf\": {\n      \"description\": \"Extraia páginas específicas de um arquivo PDF usando números de página ou intervalos (por exemplo, 1,5-8)\",\n      \"name\": \"Dividir PDF\",\n      \"shortDescription\": \"Extrair páginas específicas de um arquivo PDF\"\n    }\n  },\n  \"resultFooter\": {\n    \"copy\": \"Copiar para a área de transferência\",\n    \"download\": \"Download\"\n  },\n  \"string\": {\n    \"createPalindrome\": {\n      \"description\": \"O utilitário baseado em navegador mais simples do mundo para criar palíndromos a partir de qualquer texto. Insira texto e transforme-o instantaneamente em um palíndromo com a mesma leitura de trás para frente. Perfeito para jogos de palavras, criação de padrões de texto simétricos ou exploração de curiosidades linguísticas.\",\n      \"name\": \"Criar palíndromo\",\n      \"shortDescription\": \"Crie um texto que seja lido da mesma forma para frente e para trás\"\n    },\n    \"palindrome\": {\n      \"description\": \"O utilitário baseado em navegador mais simples do mundo para verificar se um texto é um palíndromo. Verifique instantaneamente se o seu texto é lido da mesma forma, de trás para frente. Perfeito para quebra-cabeças de palavras, análise linguística ou validação de padrões de texto simétricos. Suporta vários delimitadores e detecção de palíndromos de várias palavras.\",\n      \"name\": \"Palíndromo\",\n      \"shortDescription\": \"Verifique se o texto é lido da mesma forma para frente e para trás\"\n    },\n    \"repeat\": {\n      \"description\": \"Esta ferramenta permite que você repita um determinado texto várias vezes com um separador opcional.\",\n      \"name\": \"Repetir texto\",\n      \"shortDescription\": \"Repita o texto várias vezes\"\n    },\n    \"reverse\": {\n      \"description\": \"O utilitário baseado em navegador mais simples do mundo para inverter texto. Insira qualquer texto e inverta-o instantaneamente, caractere por caractere. Perfeito para criar texto espelhado, analisar palíndromos ou brincar com padrões de texto. Preserva espaços e caracteres especiais durante a inversão.\",\n      \"name\": \"Reverter\",\n      \"shortDescription\": \"Inverter qualquer texto caractere por caractere\"\n    },\n    \"toMorse\": {\n      \"description\": \"O utilitário baseado em navegador mais simples do mundo para converter texto em código Morse. Carregue seu texto no formulário de entrada à esquerda e você obterá o código Morse instantaneamente na área de saída. Poderoso, gratuito e rápido. Carregue texto – obtenha código Morse.\",\n      \"name\": \"String para Morse\",\n      \"shortDescription\": \"Codifique rapidamente o texto para Morse\"\n    },\n    \"uppercase\": {\n      \"description\": \"O utilitário baseado em navegador mais simples do mundo para converter texto em letras maiúsculas. Basta inserir o texto e ele será convertido automaticamente para letras maiúsculas. Perfeito para criar títulos, enfatizar texto ou padronizar o formato de texto. Suporta vários formatos de texto e preserva caracteres especiais.\",\n      \"name\": \"Maiúsculas\",\n      \"shortDescription\": \"Converter texto em letras maiúsculas\"\n    }\n  },\n  \"toolExamples\": {\n    \"subtitle\": \"Clique para experimentar!\",\n    \"title\": \"{{title}} Exemplos\"\n  },\n  \"toolFileResult\": {\n    \"copied\": \"Arquivo copiado\",\n    \"copyFailed\": \"Falha ao copiar: {{error}}\",\n    \"loading\": \"Carregando... Isso pode demorar um momento.\",\n    \"result\": \"Resultado\"\n  },\n  \"toolHeader\": {\n    \"seeExamples\": \"Veja exemplos\"\n  },\n  \"toolLayout\": {\n    \"allToolsTitle\": \"Todos {{type}}\"\n  },\n  \"toolMultiFileResult\": {\n    \"copied\": \"Arquivo copiado\",\n    \"copyFailed\": \"Falha ao copiar: {{error}}\",\n    \"loading\": \"Carregando... Isso pode demorar um momento.\",\n    \"result\": \"Resultado\"\n  },\n  \"toolMultipleAudioInput\": {\n    \"inputTitle\": \"Entrada {{type}}\",\n    \"noFilesSelected\": \"Nenhum arquivo selecionado\"\n  },\n  \"toolMultiplePdfInput\": {\n    \"inputTitle\": \"Entrada {{type}}\",\n    \"noFilesSelected\": \"Nenhum arquivo selecionado\"\n  },\n  \"toolOptions\": {\n    \"title\": \"Opções de ferramentas\"\n  },\n  \"toolTextInput\": {\n    \"copied\": \"Texto copiado\",\n    \"copyFailed\": \"Falha ao copiar: {{error}}\",\n    \"input\": \"Texto de entrada\",\n    \"placeholder\": \"Digite seu texto aqui...\"\n  },\n  \"toolTextResult\": {\n    \"copied\": \"Texto copiado\",\n    \"copyFailed\": \"Falha ao copiar: {{error}}\",\n    \"loading\": \"Carregando... Isso pode demorar um momento.\",\n    \"result\": \"Resultado\"\n  },\n  \"userTypes\": {\n    \"developers\": \"Desenvolvedores\",\n    \"generalUsers\": \"Usuários em geral\"\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/video.json",
    "content": "{\n  \"changeSpeed\": {\n    \"defaultMultiplier\": \"Multiplicador padrão: 2 significa 2x mais rápido\",\n    \"description\": \"Altere a velocidade de reprodução de arquivos de vídeo. Acelere ou desacelere vídeos, mantendo a sincronização do áudio. Suporta vários multiplicadores de velocidade e formatos de vídeo comuns.\",\n    \"inputTitle\": \"Entrada de vídeo\",\n    \"newVideoSpeed\": \"Nova velocidade de vídeo\",\n    \"resultTitle\": \"Vídeo editado\",\n    \"settingSpeed\": \"Definindo a velocidade\",\n    \"shortDescription\": \"Alterar a velocidade de reprodução do vídeo\",\n    \"title\": \"Alterar velocidade do vídeo\",\n    \"toolInfo\": {\n      \"title\": \"O que é um {{title}}?\"\n    }\n  },\n  \"compress\": {\n    \"default\": \"Padrão\",\n    \"description\": \"Compacte vídeos dimensionando-os para diferentes resoluções, como 240p, 480p, 720p, etc. Esta ferramenta ajuda a reduzir o tamanho do arquivo, mantendo uma qualidade aceitável. Suporta formatos de vídeo comuns, como MP4, WebM e OGG.\",\n    \"inputTitle\": \"Entrada de vídeo\",\n    \"loadingText\": \"Compactando vídeo...\",\n    \"lossless\": \"Sem perdas\",\n    \"quality\": \"Qualidade (CRF)\",\n    \"resolution\": \"Resolução\",\n    \"resultTitle\": \"Vídeo compactado\",\n    \"shortDescription\": \"Comprimir vídeos dimensionando-os para diferentes resoluções\",\n    \"title\": \"Comprimir vídeo\",\n    \"worst\": \"Pior\"\n  },\n  \"cropVideo\": {\n    \"cropCoordinates\": \"Coordenadas de corte\",\n    \"croppingVideo\": \"Recortando vídeo\",\n    \"description\": \"Corte o vídeo para remover áreas indesejadas.\",\n    \"errorBeyondHeight\": \"A área de corte se estende além da altura do vídeo ({{height}}px)\",\n    \"errorBeyondWidth\": \"A área de corte se estende além da largura do vídeo ({{width}}px)\",\n    \"errorCroppingVideo\": \"Erro ao cortar o vídeo. Verifique os parâmetros e o arquivo de vídeo.\",\n    \"errorLoadingDimensions\": \"Falha ao carregar as dimensões do vídeo\",\n    \"errorNonNegativeCoordinates\": \"As coordenadas X e Y devem ser não negativas\",\n    \"errorPositiveDimensions\": \"Largura e altura devem ser positivas\",\n    \"height\": \"Altura\",\n    \"inputTitle\": \"Entrada de vídeo\",\n    \"loadVideoForDimensions\": \"Carregue um vídeo para ver as dimensões\",\n    \"longDescription\": \"Esta ferramenta permite cortar arquivos de vídeo para remover áreas indesejadas ou focar em partes específicas do vídeo. Útil para remover barras pretas, ajustar proporções ou focar em conteúdo importante. Suporta vários formatos de vídeo, incluindo MP4, MOV e AVI.\",\n    \"resultTitle\": \"Vídeo recortado\",\n    \"shortDescription\": \"Recorte o vídeo para remover áreas indesejadas\",\n    \"title\": \"Cortar vídeo\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite cortar arquivos de vídeo para remover áreas indesejadas. Você pode especificar a área de corte definindo as coordenadas X, Y e as dimensões de largura e altura.\",\n      \"title\": \"Cortar vídeo\"\n    },\n    \"videoDimensions\": \"Dimensões do vídeo: {{width}} × {{height}} pixels\",\n    \"videoInformation\": \"Informações do vídeo\",\n    \"width\": \"Largura\",\n    \"xCoordinate\": \"X (esquerda)\",\n    \"yCoordinate\": \"Y (topo)\"\n  },\n  \"flip\": {\n    \"description\": \"Inverta arquivos de vídeo horizontal ou verticalmente. Espelhe vídeos para obter efeitos especiais ou corrigir problemas de orientação.\",\n    \"flippingVideo\": \"Invertendo o vídeo\",\n    \"horizontalLabel\": \"Horizontal (Espelho)\",\n    \"inputTitle\": \"Entrada de vídeo\",\n    \"orientation\": \"Orientação\",\n    \"resultTitle\": \"Vídeo invertido\",\n    \"shortDescription\": \"Inverter o vídeo horizontalmente ou verticalmente\",\n    \"title\": \"Flip Video\",\n    \"verticalLabel\": \"Vertical (de cabeça para baixo)\"\n  },\n  \"gif\": {\n    \"changeSpeed\": {\n      \"description\": \"Altere a velocidade de reprodução de animações GIF. Acelere ou desacelere GIFs, mantendo a animação suave.\",\n      \"shortDescription\": \"Alterar a velocidade da animação GIF\",\n      \"title\": \"Alterar velocidade do GIF\"\n    }\n  },\n  \"loop\": {\n    \"description\": \"Crie um vídeo em loop repetindo o vídeo original várias vezes.\",\n    \"inputTitle\": \"Entrada de vídeo\",\n    \"loopingVideo\": \"Vídeo em loop\",\n    \"loops\": \"Laços\",\n    \"numberOfLoops\": \"Número de Loops\",\n    \"resultTitle\": \"Vídeo em loop\",\n    \"shortDescription\": \"Crie arquivos de vídeo em loop\",\n    \"title\": \"Vídeo em loop\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite criar um vídeo em loop repetindo o vídeo original várias vezes. Você pode especificar quantas vezes o vídeo deve ser repetido.\",\n      \"title\": \"O que é um {{title}}?\"\n    }\n  },\n  \"mergeVideo\": {\n    \"description\": \"Combine vários arquivos de vídeo em um vídeo contínuo.\",\n    \"longDescription\": \"Esta ferramenta permite mesclar ou anexar vários arquivos de vídeo em um único vídeo contínuo. Basta enviar seus arquivos de vídeo, organizá-los na ordem desejada e mesclá-los em um único arquivo para facilitar o compartilhamento ou a edição.\",\n    \"shortDescription\": \"Adicione e mescle vídeos facilmente.\",\n    \"title\": \"Mesclar vídeos\"\n  },\n  \"rotate\": {\n    \"180Degrees\": \"180° (de cabeça para baixo)\",\n    \"270Degrees\": \"270° (90° sentido anti-horário)\",\n    \"90Degrees\": \"90° no sentido horário\",\n    \"description\": \"Gire arquivos de vídeo em 90, 180 ou 270 graus. Corrija a orientação do vídeo ou crie efeitos especiais com controle de rotação preciso.\",\n    \"inputTitle\": \"Entrada de vídeo\",\n    \"resultTitle\": \"Vídeo girado\",\n    \"rotatingVideo\": \"Vídeo rotativo\",\n    \"rotation\": \"Rotação\",\n    \"shortDescription\": \"Girar o vídeo em graus especificados\",\n    \"title\": \"Girar vídeo\"\n  },\n  \"trim\": {\n    \"description\": \"Corte arquivos de vídeo especificando os horários de início e término. Remova trechos indesejados do início ou do fim dos vídeos.\",\n    \"endTime\": \"Fim dos tempos\",\n    \"inputTitle\": \"Entrada de vídeo\",\n    \"resultTitle\": \"Vídeo recortado\",\n    \"shortDescription\": \"Corte o vídeo removendo seções indesejadas\",\n    \"startTime\": \"Hora de início\",\n    \"timestamps\": \"Carimbos de data e hora\",\n    \"title\": \"Cortar vídeo\"\n  },\n  \"videoToGif\": {\n    \"description\": \"Converta arquivos de vídeo para o formato GIF animado. Extraia intervalos de tempo específicos e crie imagens animadas compartilháveis.\",\n    \"shortDescription\": \"Converter vídeo em GIF animado\",\n    \"title\": \"Vídeo para GIF\"\n  }\n}\n"
  },
  {
    "path": "public/locales/pt/xml.json",
    "content": "{\n  \"xmlBeautifier\": {\n    \"description\": \"Formate XML com recuo e espaçamento adequados.\",\n    \"indentation\": \"Recuo\",\n    \"inputTitle\": \"XML de entrada\",\n    \"resultTitle\": \"XML embelezado\",\n    \"shortDescription\": \"Formate e embeleze o código XML\",\n    \"title\": \"Embelezador XML\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite formatar dados XML com recuo e espaçamento adequados, tornando-os mais legíveis e fáceis de trabalhar.\",\n      \"title\": \"Embelezador XML\"\n    },\n    \"useSpaces\": \"Use Espaços\",\n    \"useSpacesDescription\": \"Recuar a saída com espaços\",\n    \"useTabs\": \"Usar guias\",\n    \"useTabsDescription\": \"Recuar a saída com tabulações.\"\n  },\n  \"xmlValidator\": {\n    \"description\": \"Valide a sintaxe e a estrutura do XML.\",\n    \"placeholder\": \"Cole ou importe XML aqui...\",\n    \"shortDescription\": \"Validar código XML para erros\",\n    \"title\": \"Validador XML\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite validar a sintaxe e a estrutura do XML. Ela verifica se o XML está bem formado e fornece mensagens de erro detalhadas para quaisquer problemas encontrados.\",\n      \"title\": \"Validador XML\"\n    }\n  },\n  \"xmlViewer\": {\n    \"description\": \"Visualize e explore a estrutura XML em formato de árvore.\",\n    \"inputTitle\": \"XML de entrada\",\n    \"resultTitle\": \"Visualização em árvore XML\",\n    \"title\": \"Visualizador XML\",\n    \"toolInfo\": {\n      \"description\": \"Esta ferramenta permite que você visualize dados XML em um formato de árvore hierárquica, facilitando a exploração e a compreensão da estrutura de documentos XML.\",\n      \"title\": \"Visualizador XML\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/audio.json",
    "content": "{\n  \"changeSpeed\": {\n    \"description\": \"Измените скорость воспроизведения аудиофайлов. Ускорьте или замедлите звук, сохраняя высоту звука.\",\n    \"inputTitle\": \"Входной аудиосигнал\",\n    \"newAudioSpeed\": \"Новая скорость звука\",\n    \"outputFormat\": \"Формат вывода\",\n    \"resultTitle\": \"Отредактированный аудиофайл\",\n    \"settingSpeed\": \"Скорость установки\",\n    \"shortDescription\": \"Изменить скорость аудиофайлов\",\n    \"speedDescription\": \"Множитель по умолчанию: 2 означает в 2 раза быстрее\",\n    \"title\": \"Изменить скорость звука\",\n    \"toolInfo\": {\n      \"title\": \"Что такое {{title}}?\"\n    }\n  },\n  \"extractAudio\": {\n    \"description\": \"Извлечь звуковую дорожку из видеофайлов.\",\n    \"extractingAudio\": \"Извлечение аудио\",\n    \"inputTitle\": \"Входное видео\",\n    \"outputFormat\": \"Формат вывода\",\n    \"outputFormatDescription\": \"Выберите формат, в котором будет извлечен аудиофайл.\",\n    \"resultTitle\": \"Извлеченный аудиофайл\",\n    \"shortDescription\": \"Извлекайте аудио из видеофайлов (MP4, MOV и т. д.) в форматы AAC, MP3 или WAV.\",\n    \"title\": \"Извлечь аудио из видео\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет извлекать звуковую дорожку из видеофайлов. Вы можете выбрать различные аудиоформаты, включая AAC, MP3 и WAV.\",\n      \"title\": \"Что такое {{title}}?\"\n    }\n  },\n  \"mergeAudio\": {\n    \"description\": \"Объедините несколько аудиофайлов в один, объединив их последовательно.\",\n    \"inputTitle\": \"Входные аудиофайлы\",\n    \"longDescription\": \"Этот инструмент позволяет объединить несколько аудиофайлов в один, объединяя их в порядке загрузки. Идеально подходит для объединения фрагментов подкастов, музыкальных треков и любых других аудиофайлов, которые необходимо объединить. Поддерживает различные аудиоформаты, включая MP3, AAC и WAV.\",\n    \"mergingAudio\": \"Объединение аудио\",\n    \"outputFormat\": \"Формат вывода\",\n    \"resultTitle\": \"Объединенный аудио\",\n    \"shortDescription\": \"Объединить несколько аудиофайлов в один (MP3, AAC, WAV).\",\n    \"title\": \"Объединить аудио\",\n    \"toolInfo\": {\n      \"title\": \"Что такое {{title}}?\"\n    }\n  },\n  \"trim\": {\n    \"description\": \"Вырезайте и обрезайте аудиофайлы, чтобы извлечь определенные сегменты, указав время начала и окончания.\",\n    \"endTime\": \"Конец Времени\",\n    \"endTimeDescription\": \"Время окончания в формате ЧЧ:ММ:СС (например, 00:01:30)\",\n    \"inputTitle\": \"Входной аудиосигнал\",\n    \"longDescription\": \"Этот инструмент позволяет обрезать аудиофайлы, указав начальное и конечное время. Вы можете извлекать отдельные фрагменты из длинных аудиофайлов, удалять ненужные фрагменты или создавать более короткие клипы. Поддерживает различные аудиоформаты, включая MP3, AAC и WAV. Идеально подходит для редактирования подкастов, создания музыки и любых других задач аудиомонтажа.\",\n    \"outputFormat\": \"Формат вывода\",\n    \"resultTitle\": \"Обрезанный аудио\",\n    \"shortDescription\": \"Обрезайте аудиофайлы, извлекая определенные временные сегменты (MP3, AAC, WAV).\",\n    \"startTime\": \"Время начала\",\n    \"startTimeDescription\": \"Время начала в формате ЧЧ:ММ:СС (например, 00:00:30)\",\n    \"timeSettings\": \"Настройки времени\",\n    \"title\": \"Обрезать аудио\",\n    \"toolInfo\": {\n      \"title\": \"Что такое {{title}}?\"\n    },\n    \"trimmingAudio\": \"Обрезка аудио\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/converters.json",
    "content": "{\n  \"audioConverter\": {\n    \"title\": \"Аудиоконвертер\",\n    \"description\": \"Преобразуйте аудиофайлы между различными форматами.\",\n    \"shortDescription\": \"Преобразуйте аудиофайлы в различные форматы.\",\n    \"longDescription\": \"Этот инструмент позволяет конвертировать аудиофайлы из одного формата в другой, поддерживая широкий диапазон аудиоформатов для безперебойного преобразования.\",\n    \"outputFormat\": \"Формат вывода\",\n    \"outputFormatDescription\": \"Выберите желаемый формат выходного аудио\",\n    \"inputTitle\": \"Аудиовход\",\n    \"outputTitle\": \"Преобразованное аудио\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/csv.json",
    "content": "{\n  \"changeCsvSeparator\": {\n    \"description\": \"Измените разделитель/разделитель в CSV-файлах. Конвертируйте данные между различными форматами CSV, такими как запятая, точка с запятой, табуляция или пользовательские разделители.\",\n    \"shortDescription\": \"Изменить разделитель CSV-файла\",\n    \"title\": \"Изменить разделитель CSV\"\n  },\n  \"csvRowsToColumns\": {\n    \"description\": \"Этот инструмент преобразует строки CSV-файла (значения, разделённые запятыми) в столбцы. Он извлекает горизонтальные строки из входного CSV-файла одну за другой, поворачивает их на 90 градусов и выводит в виде вертикальных столбцов, разделённых запятыми.', longDescription: 'Этот инструмент преобразует строки CSV-файла (значения, разделённые запятыми) в столбцы. Например, если входные данные CSV-файла содержат 6 строк, то и выходные данные будут содержать 6 столбцов, а элементы строк будут расположены сверху вниз. В правильно сформированном CSV-файле количество значений в каждой строке одинаково. Однако, если в строках отсутствуют поля, программа может их исправить, и вы можете выбрать один из доступных вариантов: заполнить отсутствующие данные пустыми элементами или заменить отсутствующие данные пользовательскими элементами, такими как «missing», «?» или «x». В процессе преобразования инструмент также очищает CSV-файл от ненужной информации, такой как пустые строки (строки без видимой информации) и комментарии. Чтобы инструмент правильно распознавал комментарии, в настройках можно указать символ в начале строки, с которой начинается комментарий. Обычно это решётка «#» или двойной слеш «//». CSV-abulous!\",\n    \"longDescription\": \"Этот инструмент преобразует строки CSV-файла (файл с разделителями-запятыми) в столбцы. Например, если входные данные CSV-файла содержат 6 строк, выходные данные будут содержать 6 столбцов, а элементы строк будут расположены сверху вниз. В правильно сформированном CSV-файле количество значений в каждой строке одинаково. Однако, если в строках отсутствуют поля, программа может их исправить, и вы можете выбрать один из доступных вариантов: заполнить отсутствующие данные пустыми элементами или заменить отсутствующие данные пользовательскими элементами, например,\",\n    \"shortDescription\": \"Преобразовать строки CSV в столбцы.\",\n    \"title\": \"Преобразование строк CSV в столбцы\"\n  },\n  \"csvToJson\": {\n    \"columnSeparator\": \"Разделитель столбцов (например, , ; \\\\t)\",\n    \"commentSymbol\": \"Символ комментария (например, #)\",\n    \"conversionOptions\": \"Варианты конвертации\",\n    \"description\": \"Конвертируйте CSV-файлы в формат JSON с настраиваемыми параметрами разделителей, кавычек и форматирования вывода. Поддержка заголовков, комментариев и динамического преобразования типов.\",\n    \"dynamicTypes\": \"Динамические типы\",\n    \"dynamicTypesDescription\": \"Автоматически преобразовывать числа и логические значения\",\n    \"error\": \"Ошибка\",\n    \"errorParsing\": \"Ошибка анализа CSV: {{error}}\",\n    \"fieldQuote\": \"Поле Цитата (например, \\\")\",\n    \"inputCsvFormat\": \"Входной формат CSV\",\n    \"inputTitle\": \"Входной CSV-файл\",\n    \"invalidCsvFormat\": \"Неверный формат CSV\",\n    \"resultTitle\": \"Выходной JSON\",\n    \"shortDescription\": \"Конвертируйте данные CSV в формат JSON.\",\n    \"skipEmptyLines\": \"Пропускать пустые строки\",\n    \"skipEmptyLinesDescription\": \"Игнорировать пустые строки во входном CSV-файле\",\n    \"title\": \"Конвертировать CSV в JSON\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент преобразует файлы CSV (Comma Separated Values, разделённые запятыми) в структуры данных JavaScript Object Notation (JSON). Он поддерживает различные форматы CSV с настраиваемыми разделителями, кавычками и символами комментариев. Конвертер может обрабатывать первую строку как заголовок, пропускать пустые строки и автоматически определять типы данных, такие как числа и логические значения. Полученный JSON можно использовать для миграции данных, резервного копирования или в качестве входных данных для других приложений.\",\n      \"title\": \"Что такое конвертер CSV в JSON?\"\n    },\n    \"useHeaders\": \"Использовать заголовки\",\n    \"useHeadersDescription\": \"Относиться к первой строке как к заголовкам столбцов\"\n  },\n  \"csvToTsv\": {\n    \"description\": \"Загрузите CSV-файл в форму ниже, и он будет автоматически преобразован в TSV-файл. В настройках инструмента вы можете настроить формат входного CSV-файла: указать разделитель полей, символ кавычек и символ комментария, а также включить пропуск пустых строк CSV-файла и выбрать, сохранять ли заголовки столбцов CSV-файла.\",\n    \"longDescription\": \"Этот инструмент преобразует данные из формата CSV (Comma Separated Values) в данные из формата TSV (Tab Separated Values). CSV и TSV — популярные форматы файлов для хранения табличных данных, но в них используются разные разделители для разделения значений: в CSV используются запятые (\",\n    \"shortDescription\": \"Конвертировать данные CSV в формат TSV.\",\n    \"title\": \"Конвертировать CSV в TSV\"\n  },\n  \"csvToXml\": {\n    \"description\": \"Конвертируйте CSV-файлы в формат XML с настраиваемыми параметрами.\",\n    \"shortDescription\": \"Конвертировать данные CSV в формат XML.\",\n    \"title\": \"Конвертировать CSV в XML\"\n  },\n  \"csvToYaml\": {\n    \"description\": \"Просто загрузите CSV-файл в форму ниже, и он автоматически будет преобразован в YAML-файл. В настройках инструмента вы можете указать символ разделителя полей, символ кавычки и символ комментария, чтобы адаптировать инструмент к пользовательским форматам CSV. Кроме того, вы можете выбрать выходной формат YAML: с сохранением заголовков CSV или без них.\",\n    \"longDescription\": \"Этот инструмент преобразует данные CSV (значения, разделенные запятыми) в данные YAML (еще один язык разметки). CSV — это простой табличный формат, используемый для представления матричных типов данных, состоящих из строк и столбцов. YAML, с другой стороны, — более продвинутый формат (фактически, надмножество JSON), который создает более удобочитаемые данные для сериализации и поддерживает списки, словари и вложенные объекты. Эта программа поддерживает различные форматы входных CSV: входные данные могут быть разделены запятыми (по умолчанию), точкой с запятой, вертикальной чертой или использовать совершенно другой разделитель. Вы можете указать точный разделитель, который будут использовать ваши данные, в параметрах. Аналогично, в параметрах вы можете указать символ кавычек, который будет использоваться для переноса полей CSV (по умолчанию это символ двойной кавычки). Вы также можете пропускать строки, начинающиеся с комментариев, указав символы комментариев в параметрах. Это позволяет поддерживать чистоту данных, пропуская ненужные строки. Существует два способа преобразования CSV в YAML. Первый метод преобразует каждую строку CSV в список YAML. Второй метод извлекает заголовки из первой строки CSV и создает объекты YAML с ключами на основе этих заголовков. Вы также можете настроить выходной формат YAML, указав количество пробелов для отступов в структурах YAML. Если вам необходимо выполнить обратное преобразование, то есть преобразовать YAML в CSV, вы можете воспользоваться нашим инструментом для преобразования YAML в CSV. Csv-abulous!\",\n    \"shortDescription\": \"Быстро конвертируйте CSV-файл в YAML-файл.\",\n    \"title\": \"Конвертировать CSV в YAML\"\n  },\n  \"findIncompleteCsvRecords\": {\n    \"checkingOptions\": \"Проверка параметров\",\n    \"commentCharacterDescription\": \"Введите символ начала строки комментария. Строки, начинающиеся с этого символа, будут пропущены.\",\n    \"csvInputOptions\": \"Параметры ввода CSV\",\n    \"csvSeparatorDescription\": \"Введите символ, используемый для разделения столбцов во входном CSV-файле.\",\n    \"deleteLinesWithNoData\": \"Удалить строки без данных\",\n    \"deleteLinesWithNoDataDescription\": \"Удалить пустые строки из входного CSV-файла.\",\n    \"description\": \"Просто загрузите CSV-файл в форму ниже, и этот инструмент автоматически проверит наличие всех пропущенных значений в строках и столбцах. В настройках инструмента можно настроить формат входного файла (указать разделитель, символ кавычки и символ комментария). Кроме того, можно включить проверку на наличие пустых значений, пропускать пустые строки и установить ограничение на количество сообщений об ошибках в выходных данных.\",\n    \"findEmptyValues\": \"Найти пустые значения\",\n    \"findEmptyValuesDescription\": \"Вывести сообщение о пустых полях CSV (это не отсутствующие поля, а поля, которые ничего не содержат).\",\n    \"inputTitle\": \"Входной CSV-файл\",\n    \"limitNumberOfMessages\": \"Ограничить количество сообщений\",\n    \"messageLimitDescription\": \"Установите ограничение на количество сообщений в выводе.\",\n    \"quoteCharacterDescription\": \"Введите символ кавычек, используемый для заключения в кавычки полей ввода CSV.\",\n    \"resultTitle\": \"Статус CSV\",\n    \"shortDescription\": \"Быстро находите строки и столбцы в CSV-файле, в которых отсутствуют значения.\",\n    \"title\": \"Найти неполные записи CSV\",\n    \"toolInfo\": {\n      \"title\": \"Что такое {{title}}?\"\n    }\n  },\n  \"insertCsvColumns\": {\n    \"appendColumns\": \"Добавить столбцы\",\n    \"commentCharacterDescription\": \"Введите символ начала строки комментария. Строки, начинающиеся с этого символа, будут пропущены.\",\n    \"csvOptions\": \"Параметры CSV\",\n    \"csvSeparator\": \"CSV-разделитель\",\n    \"csvToInsert\": \"CSV для вставки\",\n    \"csvToInsertDescription\": \"Введите один или несколько столбцов, которые вы хотите вставить в CSV-файл. Символ, используемый для разделения столбцов, должен совпадать с символом во входном CSV-файле. P.S. Пустые строки будут игнорироваться.\",\n    \"customFillDescription\": \"Если входной CSV-файл неполный (отсутствуют значения), то можно ли добавить пустые поля или пользовательские символы в записи, чтобы создать правильно сформированный CSV-файл?\",\n    \"customFillValueDescription\": \"Используйте это пользовательское значение для заполнения отсутствующих полей. (Работает только с режимом «Пользовательские значения», описанным выше.)\",\n    \"customPosition\": \"Пользовательская позиция\",\n    \"customPositionOptionsDescription\": \"Выберите способ вставки столбцов в CSV-файл.\",\n    \"description\": \"Добавить новые столбцы в данные CSV в указанных позициях.\",\n    \"fillWithCustomValues\": \"Заполнить таможенные значения\",\n    \"fillWithEmptyValues\": \"Заполнить пустыми значениями\",\n    \"headerName\": \"Имя заголовка\",\n    \"headerNameDescription\": \"Заголовок столбца, после которого вы хотите вставить столбцы.\",\n    \"inputTitle\": \"Входной CSV-файл\",\n    \"insertingPositionDescription\": \"Укажите, куда вставлять столбцы в CSV-файле.\",\n    \"position\": \"Позиция\",\n    \"positionOptions\": \"Варианты позиций\",\n    \"prependColumns\": \"Добавить столбцы\",\n    \"quoteCharDescription\": \"Введите символ кавычек, используемый для заключения в кавычки полей ввода CSV.\",\n    \"resultTitle\": \"Вывод CSV\",\n    \"rowNumberDescription\": \"Номер столбца, после которого вы хотите вставить столбцы.\",\n    \"separatorDescription\": \"Введите символ, используемый для разделения столбцов во входном CSV-файле.\",\n    \"shortDescription\": \"Быстро вставляйте один или несколько новых столбцов в любое место CSV-файла.\",\n    \"title\": \"Вставить столбцы CSV\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет добавлять новые столбцы в CSV-данные в заданных позициях. Вы можете добавлять столбцы в начало, конец или вставлять их в произвольные позиции, используя названия заголовков или номера столбцов.\",\n      \"title\": \"Вставить столбцы CSV\"\n    }\n  },\n  \"swapCsvColumns\": {\n    \"description\": \"Просто загрузите CSV-файл в форму ниже, укажите столбцы для замены, и инструмент автоматически изменит положение указанных столбцов в выходном файле. В настройках инструмента вы можете указать положение или названия столбцов, которые хотите заменить, а также исправить неполные данные и при необходимости удалить пустые и закомментированные записи.\",\n    \"longDescription\": \"Этот инструмент реорганизует данные CSV, меняя местами столбцы. Перестановка столбцов может улучшить читаемость CSV-файла, размещая часто используемые данные вместе или в начале для более удобного сравнения и редактирования. Например, можно поменять местами первый столбец с последним или второй столбец с третьим. Чтобы поменять местами столбцы, выберите\",\n    \"shortDescription\": \"Изменить порядок столбцов CSV.\",\n    \"title\": \"Поменять местами столбцы CSV\"\n  },\n  \"transposeCsv\": {\n    \"description\": \"Просто загрузите свой CSV-файл в форму ниже, и этот инструмент автоматически транспонирует его. В настройках инструмента вы можете указать символ, с которого начинаются строки комментариев в CSV-файле, чтобы удалить их. Кроме того, если CSV-файл неполный (содержат пропущенные значения), вы можете заменить пропущенные значения пустым символом или пользовательским символом.\",\n    \"longDescription\": \"Этот инструмент транспонирует данные, разделенные запятыми (CSV). Он обрабатывает CSV как матрицу данных и переворачивает все элементы по главной диагонали. Выходные данные содержат те же данные CSV, что и входные, но теперь все строки стали столбцами, а все столбцы — строками. После транспонирования CSV-файл будет иметь противоположные размеры. Например, если входной файл содержит 4 столбца и 3 строки, выходной файл будет содержать 3 столбца и 4 строки. Во время преобразования программа также очищает данные от ненужных строк и исправляет неполные данные. В частности, инструмент автоматически удаляет все пустые записи и комментарии, начинающиеся с определенного символа, который можно задать в настройках. Кроме того, в случае повреждения или потери данных CSV-файла утилита дополняет файл пустыми полями или пользовательскими полями, которые можно указать в настройках. CSV-abulous!\",\n    \"shortDescription\": \"Быстро транспонируйте CSV-файл.\",\n    \"title\": \"Транспонировать CSV\"\n  },\n  \"tsvToJson\": {\n    \"description\": \"Преобразуйте данные TSV (значения, разделенные табуляцией) в формат JSON. Преобразуйте табличные данные в структурированные объекты JSON.\",\n    \"shortDescription\": \"Конвертировать формат TSV в JSON\",\n    \"title\": \"TSV в JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/image.json",
    "content": "{\n  \"changeColors\": {\n    \"description\": \"Мир\",\n    \"shortDescription\": \"Быстро поменяйте цвета на изображении\",\n    \"title\": \"Изменить цвета на изображении\"\n  },\n  \"changeOpacity\": {\n    \"description\": \"Легко настраивайте прозрачность изображений. Просто загрузите изображение, установите желаемый уровень непрозрачности с помощью ползунка от 0 (полностью прозрачно) до 1 (полностью непрозрачно) и скачайте изменённое изображение.\",\n    \"shortDescription\": \"Отрегулируйте прозрачность изображений\",\n    \"title\": \"Изменить непрозрачность изображения\"\n  },\n  \"compress\": {\n    \"compressedSize\": \"Сжатый размер\",\n    \"compressionOptions\": \"Параметры сжатия\",\n    \"description\": \"Уменьшите размер файла изображения, сохранив качество.\",\n    \"failedToCompress\": \"Не удалось сжать изображение. Попробуйте ещё раз.\",\n    \"fileSizes\": \"Размеры файлов\",\n    \"inputTitle\": \"Входное изображение\",\n    \"maxFileSizeDescription\": \"Максимальный размер файла в мегабайтах\",\n    \"originalSize\": \"Оригинальный размер\",\n    \"qualityDescription\": \"Процент качества изображения (чем ниже, тем меньше размер файла)\",\n    \"resultTitle\": \"Сжатое изображение\",\n    \"shortDescription\": \"Сжимайте изображения, чтобы уменьшить размер файла, сохраняя при этом приемлемое качество.\",\n    \"title\": \"Сжать изображение\"\n  },\n  \"compressPng\": {\n    \"description\": \"Это программа для сжатия изображений в формате PNG. После того, как вы вставите изображение в поле ввода, программа сожмёт его и отобразит результат в поле вывода. В настройках можно настроить степень сжатия, а также посмотреть старый и новый размеры файла изображения.\",\n    \"shortDescription\": \"Быстро сжать PNG\",\n    \"title\": \"Сжать png\"\n  },\n  \"convertJgpToPng\": {\n    \"description\": \"Быстро конвертируйте изображения JPG в PNG. Просто импортируйте изображение PNG в редактор слева.\",\n    \"shortDescription\": \"Быстро конвертируйте изображения JPG в PNG\",\n    \"title\": \"Конвертировать JPG в PNG\"\n  },\n  \"convertToJpg\": {\n    \"description\": \"Конвертируйте различные форматы изображений (PNG, GIF, TIF, PSD, SVG, WEBP, HEIC, RAW) в JPG с настраиваемыми параметрами качества и цвета фона.\",\n    \"shortDescription\": \"Конвертируйте изображения в JPG с контролем качества\",\n    \"title\": \"Конвертировать изображения в JPG\"\n  },\n  \"createTransparent\": {\n    \"description\": \"Мир\",\n    \"shortDescription\": \"Быстро сделать изображение прозрачным\",\n    \"title\": \"Создать прозрачный PNG\"\n  },\n  \"crop\": {\n    \"description\": \"Обрезайте изображения, чтобы удалить нежелательные области.\",\n    \"inputTitle\": \"Входное изображение\",\n    \"resultTitle\": \"Обрезанное изображение\",\n    \"shortDescription\": \"Быстро обрезайте изображения.\",\n    \"title\": \"Обрезать изображение\"\n  },\n  \"editor\": {\n    \"description\": \"Расширенный редактор изображений с инструментами для обрезки, поворота, аннотирования, настройки цветов и добавления водяных знаков. Редактируйте изображения с помощью профессиональных инструментов прямо в браузере.\",\n    \"shortDescription\": \"Редактируйте изображения с помощью расширенных инструментов и функций\",\n    \"title\": \"Редактор изображений\"\n  },\n  \"imageToText\": {\n    \"description\": \"Извлекайте текст из изображений (JPG, PNG) с помощью оптического распознавания символов (OCR).\",\n    \"shortDescription\": \"Извлекайте текст из изображений с помощью OCR.\",\n    \"title\": \"Изображение в текст (OCR)\"\n  },\n  \"qrCode\": {\n    \"description\": \"Создавайте QR-коды для различных типов данных: URL, текст, электронная почта, телефон, SMS, Wi-Fi, vCard и т. д.\",\n    \"shortDescription\": \"Создавайте индивидуальные QR-коды для различных форматов данных.\",\n    \"title\": \"Генератор QR-кода\"\n  },\n  \"removeBackground\": {\n    \"description\": \"Мир\",\n    \"shortDescription\": \"Автоматически удалять фоны с изображений\",\n    \"title\": \"Удалить фон из изображения\"\n  },\n  \"resize\": {\n    \"description\": \"Изменяйте размеры изображений до разных размеров.\",\n    \"dimensionType\": \"Тип измерения\",\n    \"heightDescription\": \"Высота (в пикселях)\",\n    \"inputTitle\": \"Входное изображение\",\n    \"maintainAspectRatio\": \"Сохранять соотношение сторон\",\n    \"maintainAspectRatioDescription\": \"Сохраните исходное соотношение сторон изображения.\",\n    \"percentage\": \"Процент\",\n    \"percentageDescription\": \"Процент от исходного размера (например, 50 для половинного размера, 200 для двойного размера)\",\n    \"resizeByPercentage\": \"Изменить размер в процентах\",\n    \"resizeByPercentageDescription\": \"Измените размер, указав процент от исходного размера.\",\n    \"resizeByPixels\": \"Изменить размер по пикселям\",\n    \"resizeByPixelsDescription\": \"Измените размер, указав размеры в пикселях.\",\n    \"resizeMethod\": \"Метод изменения размера\",\n    \"resultTitle\": \"Измененное изображение\",\n    \"setHeight\": \"Установить высоту\",\n    \"setHeightDescription\": \"Укажите высоту в пикселях и рассчитайте ширину на основе соотношения сторон.\",\n    \"setWidth\": \"Установить ширину\",\n    \"setWidthDescription\": \"Укажите ширину в пикселях и рассчитайте высоту на основе соотношения сторон.\",\n    \"shortDescription\": \"Легко изменяйте размер изображений.\",\n    \"title\": \"Изменить размер изображения\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет изменять размер изображений JPG, PNG, SVG или GIF. Размер можно изменять, указывая значения в пикселях или процентах, с возможностью сохранения исходного соотношения сторон.\",\n      \"title\": \"Изменить размер изображения\"\n    },\n    \"widthDescription\": \"Ширина (в пикселях)\"\n  },\n  \"rotate\": {\n    \"description\": \"Повернуть изображение на указанный угол.\",\n    \"shortDescription\": \"Легко поворачивайте изображение.\",\n    \"title\": \"Повернуть изображение\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/json.json",
    "content": "{\n  \"comparison\": {\n    \"description\": \"Сравните два объекта JSON, чтобы выявить различия в структуре и значениях.\",\n    \"shortDescription\": \"Найти различия между двумя объектами JSON\",\n    \"title\": \"Сравнить JSON\"\n  },\n  \"escapeJson\": {\n    \"description\": \"Экранируйте специальные символы в строках JSON. Преобразуйте данные JSON в корректно экранированный формат для безопасной передачи и хранения.\",\n    \"shortDescription\": \"Экранирование специальных символов в JSON\",\n    \"title\": \"Escape JSON\"\n  },\n  \"jsonToXml\": {\n    \"description\": \"Преобразуйте данные JSON в формат XML. Преобразуйте структурированные объекты JSON в корректные XML-документы.\",\n    \"shortDescription\": \"Конвертировать формат JSON в XML\",\n    \"title\": \"JSON в XML\"\n  },\n  \"minify\": {\n    \"description\": \"Удалите все ненужные пробелы из JSON.\",\n    \"inputTitle\": \"Входной JSON\",\n    \"resultTitle\": \"Минимизированный JSON\",\n    \"shortDescription\": \"Минимизируйте JSON, удалив пробелы\",\n    \"title\": \"Минифицировать JSON\",\n    \"toolInfo\": {\n      \"description\": \"Минификация JSON — это процесс удаления всех ненужных пробелов из JSON-данных с сохранением их корректности. Это включает в себя удаление пробелов, переносов строк и отступов, которые не требуются для корректного анализа JSON-данных. Минификация уменьшает размер JSON-данных, делая их более эффективными для хранения и передачи, сохраняя при этом прежнюю структуру данных и значения.\",\n      \"title\": \"Что такое минификация JSON?\"\n    }\n  },\n  \"prettify\": {\n    \"description\": \"Отформатируйте JSON с правильными отступами и пробелами.\",\n    \"indentation\": \"Отступ\",\n    \"inputTitle\": \"Входной JSON\",\n    \"resultTitle\": \"Упрощенный JSON\",\n    \"shortDescription\": \"Форматируйте и украшайте JSON-код\",\n    \"title\": \"Упрощение JSON\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет форматировать данные JSON с правильными отступами и интервалами, что делает их более читабельными и удобными для работы.\",\n      \"title\": \"Упрощение JSON\"\n    },\n    \"useSpaces\": \"Используйте пробелы\",\n    \"useSpacesDescription\": \"Отступ вывода с пробелами\",\n    \"useTabs\": \"Использовать вкладки\",\n    \"useTabsDescription\": \"Отступ вывода с помощью табуляции.\"\n  },\n  \"stringify\": {\n    \"description\": \"Преобразуйте объекты JavaScript в строки формата JSON. Сериализуйте структуры данных в строки JSON для хранения или передачи.\",\n    \"shortDescription\": \"Преобразование объектов в строку JSON\",\n    \"title\": \"Строкификация JSON\"\n  },\n  \"validateJson\": {\n    \"description\": \"Проверьте корректность и правильность формата JSON.\",\n    \"inputTitle\": \"Входной JSON\",\n    \"invalidJson\": \"❌ {{error}}\",\n    \"resultTitle\": \"Результат проверки\",\n    \"shortDescription\": \"Проверить JSON-код на наличие ошибок\",\n    \"title\": \"Проверить JSON\",\n    \"toolInfo\": {\n      \"description\": \"JSON (JavaScript Object Notation) — это облегчённый формат обмена данными. Проверка JSON гарантирует соответствие структуры данных стандарту JSON. Корректный объект JSON должен иметь: - Имена свойств, заключённые в двойные кавычки. - Правильно сбалансированные фигурные скобки {}. - Отсутствие запятых после последней пары «ключ-значение». - Корректную вложенность объектов и массивов. Этот инструмент проверяет входной JSON и предоставляет обратную связь, помогающую выявлять и исправлять распространённые ошибки.\",\n      \"title\": \"Что такое валидация JSON?\"\n    },\n    \"validJson\": \"✅ Действительный JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/list.json",
    "content": "{\n  \"duplicate\": {\n    \"concatenate\": \"Объединить\",\n    \"concatenateDescription\": \"Объединить копии (если этот флажок не установлен, элементы будут переплетены)\",\n    \"copyDescription\": \"Количество копий (может быть дробным)\",\n    \"description\": \"Самая простая в мире браузерная утилита для копирования элементов списка. Введите список и укажите критерии копирования, чтобы создать копии элементов. Идеально подходит для расширения данных, тестирования или создания повторяющихся шаблонов.\",\n    \"duplicationOptions\": \"Варианты дублирования\",\n    \"error\": \"Ошибка\",\n    \"example1Description\": \"В этом примере показано, как дублировать список слов.\",\n    \"example1Title\": \"Простое дублирование\",\n    \"example2Description\": \"В этом примере показано, как дублировать список в обратном порядке.\",\n    \"example2Title\": \"Обратное дублирование\",\n    \"example3Description\": \"В этом примере показано, как переплетать элементы, а не объединять их.\",\n    \"example3Title\": \"Переплетение предметов\",\n    \"example4Description\": \"В этом примере показано, как дублировать список с дробным числом копий.\",\n    \"example4Title\": \"Дробное дублирование\",\n    \"examples\": {\n      \"fractional\": {\n        \"description\": \"В этом примере показано, как дублировать список с дробным числом копий.\",\n        \"title\": \"Дробное дублирование\"\n      },\n      \"interweave\": {\n        \"description\": \"В этом примере показано, как переплетать элементы, а не объединять их.\",\n        \"title\": \"Переплетение предметов\"\n      },\n      \"reverse\": {\n        \"description\": \"В этом примере показано, как дублировать список в обратном порядке.\",\n        \"title\": \"Обратное дублирование\"\n      },\n      \"simple\": {\n        \"description\": \"В этом примере показано, как дублировать список слов.\",\n        \"title\": \"Простое дублирование\"\n      }\n    },\n    \"inputTitle\": \"Список входных данных\",\n    \"joinSeparatorDescription\": \"Разделитель для присоединения к дублированному списку\",\n    \"resultTitle\": \"Дублированный список\",\n    \"reverse\": \"Обеспечить регресс\",\n    \"reverseDescription\": \"Отменить дублирующиеся элементы\",\n    \"shortDescription\": \"Дублировать элементы списка с указанными критериями\",\n    \"splitByRegex\": \"Разделить по регулярному выражению\",\n    \"splitBySymbol\": \"Разделить по символу\",\n    \"splitOptions\": \"Варианты разделения\",\n    \"splitSeparatorDescription\": \"Разделитель для разделения списка\",\n    \"title\": \"Дубликат\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет дублировать элементы списка. Вы можете указать количество копий (включая дробные значения), управлять объединением или переплетением элементов и даже инвертировать дублированные элементы. Он полезен для создания повторяющихся шаблонов, генерации тестовых данных или расширения списков с предсказуемым содержимым.\",\n      \"title\": \"Дублирование списка\"\n    },\n    \"unknownError\": \"Произошла неизвестная ошибка.\",\n    \"validation\": {\n      \"copyMustBeNumber\": \"Количество копий должно быть числом.\",\n      \"copyMustBePositive\": \"Количество копий должно быть положительным.\",\n      \"copyRequired\": \"Необходимое количество копий\",\n      \"joinSeparatorRequired\": \"Разделитель соединений обязателен.\",\n      \"separatorRequired\": \"Разделитель обязателен.\"\n    }\n  },\n  \"findMostPopular\": {\n    \"description\": \"Самая простая в мире браузерная утилита для поиска самых популярных элементов в списке. Введите данные из списка и мгновенно получите элементы, которые встречаются чаще всего. Идеально подходит для анализа данных, выявления тенденций или поиска общих элементов.\",\n    \"displayFormatDescription\": \"Как отобразить самые популярные элементы списка?\",\n    \"displayOptions\": {\n      \"count\": \"Показать количество элементов\",\n      \"percentage\": \"Показать процент элемента\",\n      \"total\": \"Показать общую сумму\"\n    },\n    \"extractListItems\": \"Как извлечь элементы списка?\",\n    \"ignoreItemCase\": \"Игнорировать регистр элементов\",\n    \"ignoreItemCaseDescription\": \"Сравните все элементы списка в нижнем регистре.\",\n    \"inputTitle\": \"Список входных данных\",\n    \"itemComparison\": \"Сравнение товаров\",\n    \"outputFormat\": \"Формат вывода основного элемента\",\n    \"removeEmptyItems\": \"Удалить пустые элементы\",\n    \"removeEmptyItemsDescription\": \"Игнорировать пустые элементы при сравнении.\",\n    \"resultTitle\": \"Самые популярные товары\",\n    \"shortDescription\": \"Найти наиболее часто встречающиеся элементы\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Сортировать по алфавиту\",\n      \"count\": \"Сортировать по количеству\"\n    },\n    \"sortingMethodDescription\": \"Выберите метод сортировки.\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Разделите элементы входного списка с помощью регулярного выражения.\",\n        \"title\": \"Используйте регулярное выражение для разделения\"\n      },\n      \"symbol\": {\n        \"description\": \"Разделите элементы входного списка символом.\",\n        \"title\": \"Используйте символ для разделения\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Установите разделительный символ или регулярное выражение.\",\n    \"title\": \"Найти самые популярные\",\n    \"trimItems\": \"Обрезать верхние элементы списка\",\n    \"trimItemsDescription\": \"Удалите начальные и конечные пробелы перед сравнением элементов.\"\n  },\n  \"findUnique\": {\n    \"caseSensitiveItems\": \"Элементы, чувствительные к регистру\",\n    \"caseSensitiveItemsDescription\": \"Выводить элементы с разным регистром как уникальные элементы в списке.\",\n    \"delimiterDescription\": \"Установите разделительный символ или регулярное выражение.\",\n    \"description\": \"Самая простая в мире браузерная утилита для поиска уникальных элементов в списке. Введите данные из списка и мгновенно получите все уникальные значения, удалив дубликаты. Идеально подходит для очистки данных, дедупликации или поиска уникальных элементов.\",\n    \"findAbsolutelyUniqueItems\": \"Найдите абсолютно уникальные предметы\",\n    \"findAbsolutelyUniqueItemsDescription\": \"Отображать только те элементы списка, которые существуют в единственном экземпляре.\",\n    \"inputListDelimiter\": \"Разделитель входного списка\",\n    \"inputTitle\": \"Список входных данных\",\n    \"outputListDelimiter\": \"Разделитель выходного списка\",\n    \"resultTitle\": \"Уникальные предметы\",\n    \"shortDescription\": \"Найти уникальные элементы в списке\",\n    \"skipEmptyItems\": \"Пропустить пустые элементы\",\n    \"skipEmptyItemsDescription\": \"Не включайте пустые элементы списка в вывод.\",\n    \"title\": \"Найти уникальный\",\n    \"trimItems\": \"Элементы списка обрезки\",\n    \"trimItemsDescription\": \"Перед сравнением элементов удалите начальные и конечные пробелы.\",\n    \"uniqueItemOptions\": \"Уникальные параметры предмета\"\n  },\n  \"group\": {\n    \"deleteEmptyItems\": \"Удалить пустые элементы\",\n    \"deleteEmptyItemsDescription\": \"Игнорируйте пустые элементы и не включайте их в группы.\",\n    \"description\": \"Самая простая в мире браузерная утилита для группировки элементов списка. Введите список и укажите критерии группировки, чтобы организовать элементы в логические группы. Идеально подходит для категоризации данных, организации информации и создания структурированных списков. Поддерживает настраиваемые разделители и различные варианты группировки.\",\n    \"emptyItemsAndPadding\": \"Пустые предметы и заполнение\",\n    \"groupNumberDescription\": \"Количество предметов в группе\",\n    \"groupSeparatorDescription\": \"Символ разделителя групп\",\n    \"groupSizeAndSeparators\": \"Размер группы и разделители\",\n    \"inputItemSeparator\": \"Разделитель входных элементов\",\n    \"inputTitle\": \"Список входных данных\",\n    \"itemSeparatorDescription\": \"Символ разделителя элементов\",\n    \"leftWrapDescription\": \"Символ левого переноса группы.\",\n    \"padNonFullGroups\": \"Группы с неполным заполнением\",\n    \"padNonFullGroupsDescription\": \"Заполните неполные группы пользовательским элементом (введите ниже).\",\n    \"paddingCharDescription\": \"Используйте этот символ или элемент для заполнения неполных групп.\",\n    \"resultTitle\": \"Сгруппированные элементы\",\n    \"rightWrapDescription\": \"Правый символ переноса группы.\",\n    \"shortDescription\": \"Группировать элементы списка по общим свойствам\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Разделите элементы входного списка с помощью регулярного выражения.\",\n        \"title\": \"Используйте регулярное выражение для разделения\"\n      },\n      \"symbol\": {\n        \"description\": \"Разделите элементы входного списка символом.\",\n        \"title\": \"Используйте символ для разделения\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Установите разделительный символ или регулярное выражение.\",\n    \"title\": \"Группа\"\n  },\n  \"reverse\": {\n    \"description\": \"Это очень простое браузерное приложение, которое выводит все элементы списка в обратном порядке. Элементы ввода можно разделять любым символом, и вы также можете изменить разделитель для элементов перевёрнутого списка.\",\n    \"inputTitle\": \"Список входных данных\",\n    \"itemSeparator\": \"Разделитель элементов\",\n    \"itemSeparatorDescription\": \"Установите разделительный символ или регулярное выражение.\",\n    \"outputListOptions\": \"Параметры выходного списка\",\n    \"outputSeparatorDescription\": \"Разделитель элементов выходного списка.\",\n    \"resultTitle\": \"Обратный список\",\n    \"shortDescription\": \"Быстро перевернуть список\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Разделите элементы входного списка с помощью регулярного выражения.\",\n        \"title\": \"Используйте регулярное выражение для разделения\"\n      },\n      \"symbol\": {\n        \"description\": \"Разделяйте элементы входного списка символами.\",\n        \"title\": \"Используйте символ для разделения\"\n      }\n    },\n    \"splitterMode\": \"Режим разделителя\",\n    \"title\": \"Обеспечить регресс\",\n    \"toolInfo\": {\n      \"description\": \"С помощью этой утилиты вы можете изменить порядок элементов в списке на обратный. Утилита сначала разбивает входной список на отдельные элементы, а затем проходит по ним от последнего к первому, выводя каждый элемент на выход. Входной список может содержать любые текстовые данные, включая цифры, числа, строки, слова, предложения и т. д. Разделителем элементов входного списка также может быть регулярное выражение. Например, регулярное выражение /[;,]/ позволит использовать элементы, разделённые запятыми или точкой с запятой. Разделители элементов входного и выходного списков можно настроить в параметрах. По умолчанию как входной, так и выходной списки разделяются запятыми. Listabulous!\",\n      \"title\": \"Что такое реверс списка?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"Самая простая в мире браузерная утилита для поворота элементов списка. Введите список и укажите величину поворота, чтобы сместить элементы на указанное количество позиций. Идеально подходит для обработки данных, циклических сдвигов или изменения порядка списков.\",\n    \"shortDescription\": \"Повернуть элементы списка по указанным позициям\",\n    \"title\": \"Повернуть\"\n  },\n  \"shuffle\": {\n    \"delimiterDescription\": \"Установите разделительный символ или регулярное выражение.\",\n    \"description\": \"Самая простая в мире браузерная утилита для перемешивания элементов списка. Введите свой список и мгновенно получите его случайную версию с элементами в случайном порядке. Идеально подходит для создания разнообразия, проверки случайности или перемешивания упорядоченных данных.\",\n    \"inputListSeparator\": \"Разделитель входного списка\",\n    \"inputTitle\": \"Список входных данных\",\n    \"joinSeparatorDescription\": \"Используйте этот разделитель в рандомизированном списке.\",\n    \"outputLengthDescription\": \"Вывести столько случайных элементов\",\n    \"resultTitle\": \"Перемешанный список\",\n    \"shortDescription\": \"Расположить элементы списка в случайном порядке\",\n    \"shuffledListLength\": \"Длина перемешанного списка\",\n    \"shuffledListSeparator\": \"Разделитель перемешанного списка\",\n    \"title\": \"Перемешать\"\n  },\n  \"sort\": {\n    \"caseSensitive\": \"Сортировка с учетом регистра\",\n    \"caseSensitiveDescription\": \"Сортируйте элементы с заглавными и строчными буквами отдельно. Заглавные буквы располагаются перед строчными в списке по возрастанию. (Работает только в режиме сортировки по алфавиту.)\",\n    \"description\": \"Самая простая в мире браузерная утилита для сортировки элементов списка. Введите список и укажите критерии сортировки, чтобы упорядочить элементы по возрастанию или убыванию. Идеально подходит для организации данных, обработки текста и создания упорядоченных списков.\",\n    \"inputItemSeparator\": \"Разделитель входных элементов\",\n    \"inputTitle\": \"Список входных данных\",\n    \"joinSeparatorDescription\": \"Используйте этот символ для объединения элементов в отсортированном списке.\",\n    \"orderDescription\": \"Выберите порядок сортировки.\",\n    \"orderOptions\": {\n      \"decreasing\": \"В порядке убывания\",\n      \"increasing\": \"По возрастанию\"\n    },\n    \"removeDuplicates\": \"Удалить дубликаты\",\n    \"removeDuplicatesDescription\": \"Удалить дублирующиеся элементы списка.\",\n    \"resultTitle\": \"Сортированный список\",\n    \"shortDescription\": \"Сортировать элементы списка в указанном порядке\",\n    \"sortMethod\": \"Метод сортировки\",\n    \"sortMethodDescription\": \"Выберите метод сортировки.\",\n    \"sortOptions\": {\n      \"alphabetic\": \"Сортировать по алфавиту\",\n      \"length\": \"Сортировать по длине\",\n      \"numeric\": \"Сортировка по числам\"\n    },\n    \"sortedItemProperties\": \"Сортированные свойства элементов\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"Разделите элементы входного списка с помощью регулярного выражения.\",\n        \"title\": \"Используйте регулярное выражение для разделения\"\n      },\n      \"symbol\": {\n        \"description\": \"Разделяйте элементы входного списка символами.\",\n        \"title\": \"Используйте символ для разделения\"\n      }\n    },\n    \"splitSeparatorDescription\": \"Установите разделительный символ или регулярное выражение.\",\n    \"title\": \"Сортировать\"\n  },\n  \"truncate\": {\n    \"description\": \"Самая простая в мире браузерная утилита для обрезки списков. Введите список и укажите максимальное количество элементов, которые нужно сохранить. Идеально подходит для обработки данных, управления списками или ограничения длины контента.\",\n    \"shortDescription\": \"Сократить список до указанного количества элементов\",\n    \"title\": \"Усечение\"\n  },\n  \"unwrap\": {\n    \"description\": \"Самая простая в мире браузерная утилита для развёртывания элементов списка. Введите свой развёрнутый список и укажите критерии развёртывания, чтобы сделать упорядоченные элементы плоскими. Идеально подходит для обработки данных, работы с текстом или извлечения контента из структурированных списков.\",\n    \"shortDescription\": \"Извлечь элементы списка из структурированного формата\",\n    \"title\": \"Развернуть\"\n  },\n  \"wrap\": {\n    \"description\": \"Добавьте текст до и после каждого элемента списка.\",\n    \"inputTitle\": \"Список входных данных\",\n    \"joinSeparatorDescription\": \"Разделитель для присоединения к упакованному списку\",\n    \"leftTextDescription\": \"Текст, который нужно добавить перед каждым элементом\",\n    \"removeEmptyItems\": \"Удалить пустые элементы\",\n    \"resultTitle\": \"Обернутый список\",\n    \"rightTextDescription\": \"Текст, который нужно добавить после каждого элемента\",\n    \"shortDescription\": \"Обернуть элементы списка по указанным критериям\",\n    \"splitByRegex\": \"Разделить по регулярному выражению\",\n    \"splitBySymbol\": \"Разделить по символу\",\n    \"splitOptions\": \"Варианты разделения\",\n    \"splitSeparatorDescription\": \"Разделитель для разделения списка\",\n    \"title\": \"Сворачивать\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет добавлять текст перед каждым элементом списка и после него. Вы можете указать разный текст для левой и правой частей списка и управлять обработкой списка. Он полезен для добавления кавычек, скобок и другого форматирования элементов списка, подготовки данных для различных форматов или создания структурированного текста.\",\n      \"title\": \"Перенос списка\"\n    },\n    \"wrapOptions\": \"Варианты обертывания\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/number.json",
    "content": "{\n  \"arithmeticSequence\": {\n    \"commonDifferenceDescription\": \"Общее различие между терминами (г)\",\n    \"description\": \"Генерация арифметических последовательностей с настраиваемыми параметрами.\",\n    \"firstTermDescription\": \"Первый член последовательности (a₁)\",\n    \"numberOfTermsDescription\": \"Количество генерируемых терминов (n)\",\n    \"outputFormat\": \"Формат вывода\",\n    \"resultTitle\": \"Сгенерированная последовательность\",\n    \"separatorDescription\": \"Разделитель между терминами\",\n    \"sequenceParameters\": \"Параметры последовательности\",\n    \"shortDescription\": \"Генерация арифметических последовательностей\",\n    \"title\": \"Арифметическая последовательность\",\n    \"toolInfo\": {\n      \"description\": \"Арифметическая прогрессия — это последовательность чисел, в которой разность между каждым последующим членом постоянна. Эта постоянная разность называется разностью. Зная первый член (a₁) и разность (d), можно найти каждый член, прибавив разность к предыдущему.\",\n      \"title\": \"Что такое арифметическая последовательность?\"\n    }\n  },\n  \"generate\": {\n    \"arithmeticSequenceOption\": \"Вариант арифметической последовательности\",\n    \"description\": \"Сгенерируйте последовательность чисел с настраиваемыми параметрами.\",\n    \"numberOfElementsDescription\": \"Количество элементов в последовательности.\",\n    \"resultTitle\": \"Сгенерированные числа\",\n    \"separator\": \"Разделитель\",\n    \"separatorDescription\": \"Разделите элементы в арифметической последовательности этим символом.\",\n    \"shortDescription\": \"Генерация случайных чисел в указанных диапазонах\",\n    \"startSequenceDescription\": \"Начните последовательность с этого числа.\",\n    \"stepDescription\": \"Увеличьте каждый элемент на эту величину\",\n    \"title\": \"Генерировать\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет генерировать последовательность чисел с настраиваемыми параметрами. Вы можете указать начальное значение, размер шага и количество элементов.\",\n      \"title\": \"Генерировать числа\"\n    }\n  },\n  \"ohmsLaw\": {\n    \"description\": \"Рассчитывает напряжение, ток и сопротивление\",\n    \"longDescription\": \"Этот калькулятор использует закон Ома (V = I × R) для определения любого из трёх электрических параметров при известных двух других. Закон Ома — фундаментальный принцип электротехники, описывающий взаимосвязь между напряжением (V), током (I) и сопротивлением (R). Этот инструмент незаменим для любителей электроники, инженеров-электриков и студентов, работающих с электрическими схемами, для быстрого нахождения неизвестных значений в своих электрических схемах.\",\n    \"shortDescription\": \"Рассчитайте напряжение, ток или сопротивление в электрических цепях, используя закон Ома.\",\n    \"title\": \"Закон Ома\"\n  },\n  \"randomNumberGenerator\": {\n    \"description\": \"Генерация случайных чисел в указанном диапазоне с настраиваемыми параметрами.\",\n    \"error\": {\n      \"generationFailed\": \"Не удалось сгенерировать случайные числа. Проверьте входные параметры.\"\n    },\n    \"info\": {\n      \"description\": \"Генератор случайных чисел создаёт непредсказуемые числа в заданном диапазоне. Этот инструмент использует криптографически безопасную генерацию случайных чисел, чтобы гарантировать получение действительно случайных результатов. Он полезен для моделирования, игр, статистической выборки и тестирования.\",\n      \"title\": \"Что такое генератор случайных чисел?\"\n    },\n    \"longDescription\": \"Генерирует случайные числа в заданном диапазоне, включая целые и десятичные, разрешает или предотвращает дубликаты, а также сортирует результаты. Идеально подходит для моделирования, тестирования, игр и статистического анализа.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDecimals\": {\n          \"description\": \"Генерировать десятичные числа вместо целых\",\n          \"title\": \"Разрешить десятичные числа\"\n        },\n        \"allowDuplicates\": {\n          \"description\": \"Разрешить одному и тому же номеру появляться несколько раз\",\n          \"title\": \"Разрешить дубликаты\"\n        },\n        \"countDescription\": \"Количество случайных чисел для генерации (1–10 000)\",\n        \"sortResults\": {\n          \"description\": \"Сортировать сгенерированные числа в порядке возрастания\",\n          \"title\": \"Сортировать результаты\"\n        },\n        \"title\": \"Варианты генерации\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Символ(ы) для разделения сгенерированных чисел\",\n        \"title\": \"Настройки вывода\"\n      },\n      \"range\": {\n        \"maxDescription\": \"Максимальное значение (включительно)\",\n        \"minDescription\": \"Минимальное значение (включительно)\",\n        \"title\": \"Настройки диапазона\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Считать\",\n      \"hasDuplicates\": \"Содержит дубликаты\",\n      \"isSorted\": \"Сортировано\",\n      \"range\": \"Диапазон\",\n      \"title\": \"Сгенерированные случайные числа\"\n    },\n    \"shortDescription\": \"Генерация случайных чисел в заданных диапазонах\",\n    \"title\": \"Генератор случайных чисел\"\n  },\n  \"randomPortGenerator\": {\n    \"description\": \"Генерация случайных сетевых портов в указанных диапазонах с настраиваемыми параметрами.\",\n    \"error\": {\n      \"generationFailed\": \"Не удалось сгенерировать случайные порты. Проверьте входные параметры.\"\n    },\n    \"info\": {\n      \"description\": \"Генератор случайных портов создаёт непредсказуемые номера сетевых портов в заданных диапазонах. Этот инструмент соответствует стандартам номеров портов IANA и включает в себя идентификацию распространённых служб. Полезно для разработки, тестирования, настройки сети и предотвращения конфликтов портов.\",\n      \"title\": \"Что такое генератор случайных портов?\"\n    },\n    \"longDescription\": \"Генерация случайных сетевых портов в заданных диапазонах (известных, зарегистрированных, динамических или пользовательских). Идеально подходит для разработки, тестирования и настройки сети. Включает идентификацию служб портов для распространённых портов.\",\n    \"options\": {\n      \"generation\": {\n        \"allowDuplicates\": {\n          \"description\": \"Разрешить одному и тому же порту появляться несколько раз\",\n          \"title\": \"Разрешить дубликаты\"\n        },\n        \"countDescription\": \"Количество случайных портов для генерации (1–1000)\",\n        \"sortResults\": {\n          \"description\": \"Сортировать сгенерированные порты в порядке возрастания\",\n          \"title\": \"Сортировать результаты\"\n        },\n        \"title\": \"Варианты генерации\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"Символ(ы) для разделения сгенерированных портов\",\n        \"title\": \"Настройки вывода\"\n      },\n      \"range\": {\n        \"custom\": \"Пользовательский диапазон\",\n        \"dynamic\": \"Динамические порты (49152-65535)\",\n        \"maxPortDescription\": \"Максимальный номер порта (1-65535)\",\n        \"minPortDescription\": \"Минимальный номер порта (1-65535)\",\n        \"registered\": \"Зарегистрированные порты (1024-49151)\",\n        \"title\": \"Настройки диапазона портов\",\n        \"wellKnown\": \"Известные порты (1-1023)\"\n      }\n    },\n    \"result\": {\n      \"count\": \"Считать\",\n      \"hasDuplicates\": \"Содержит дубликаты\",\n      \"isSorted\": \"Сортировано\",\n      \"portDetails\": \"Подробности порта\",\n      \"range\": \"Порт-Рейндж\",\n      \"title\": \"Сгенерированные случайные порты\"\n    },\n    \"shortDescription\": \"Генерировать случайные сетевые порты\",\n    \"title\": \"Генератор случайных портов\"\n  },\n  \"slackline\": {\n    \"description\": \"Рассчитывает натяжение стропы\",\n    \"longDescription\": \"Этот калькулятор предполагает наличие нагрузки в центре троса.\",\n    \"shortDescription\": \"Рассчитайте примерное натяжение стропы или бельевой веревки. Не полагайтесь на это в вопросах безопасности.\",\n    \"title\": \"Натяжение стропы\"\n  },\n  \"sphereArea\": {\n    \"description\": \"Площадь сферы\",\n    \"longDescription\": \"Этот калькулятор вычисляет площадь поверхности сферы по формуле A = 4πr². Вы можете либо ввести радиус для нахождения площади поверхности, либо ввести площадь поверхности для расчёта искомого радиуса. Этот инструмент полезен для студентов, изучающих геометрию, инженеров, работающих со сферическими объектами, и всех, кому необходимо выполнять расчёты, связанные со сферическими поверхностями.\",\n    \"shortDescription\": \"Рассчитайте площадь поверхности сферы по ее радиусу.\",\n    \"title\": \"Площадь сферы\"\n  },\n  \"sphereVolume\": {\n    \"description\": \"Объем сферы\",\n    \"longDescription\": \"Этот калькулятор вычисляет объём сферы по формуле V = (4/3)πr³. Вы можете ввести радиус или диаметр для расчёта объёма, либо ввести объём для определения необходимого радиуса. Этот инструмент будет полезен студентам, инженерам и специалистам, работающим со сферическими объектами в таких областях, как физика, инженерия и производство.\",\n    \"shortDescription\": \"Рассчитайте объем сферы, используя радиус или диаметр.\",\n    \"title\": \"Объем сферы\"\n  },\n  \"sum\": {\n    \"description\": \"Вычислите сумму списка чисел. Введите числа, разделенные запятыми или символами переноса строки, чтобы получить их общую сумму.\",\n    \"example1Description\": \"В этом примере мы вычисляем сумму десяти положительных целых чисел. Эти числа расположены в столбце, и их общая сумма равна 19494.\",\n    \"example1Title\": \"Сумма десяти положительных чисел\",\n    \"example2Description\": \"В этом примере переворачивается столбец из двадцати трёхсложных существительных, и все слова выводятся снизу вверх. Для разделения элементов списка используется символ \\\\n, что означает, что каждый элемент находится на отдельной строке.\",\n    \"example2Title\": \"Посчитайте деревья в парке\",\n    \"example3Description\": \"В этом примере мы складываем девяносто различных значений: положительные и отрицательные числа, целые числа и десятичные дроби. В качестве разделителя входных данных мы устанавливаем запятую, и после сложения всех значений получаем 0.\",\n    \"example3Title\": \"Сумма целых чисел и десятичных дробей\",\n    \"example4Description\": \"В этом примере мы вычисляем сумму всех десяти цифр и включаем опцию «Печать текущей суммы». Промежуточные значения суммы получаются в процессе сложения. Таким образом, на выходе получается следующая последовательность: 0, 1 (0 + 1), 3 (0 + 1 + 2), 6 (0 + 1 + 2 + 3), 10 (0 + 1 + 2 + 3 + 4) и так далее.\",\n    \"example4Title\": \"Текущая сумма чисел\",\n    \"extractionTypes\": {\n      \"delimiter\": {\n        \"description\": \"Настройте разделитель чисел здесь. (По умолчанию это разрыв строки.)\",\n        \"title\": \"Разделитель чисел\"\n      },\n      \"smart\": {\n        \"description\": \"Автоматическое определение цифр во входных данных.\",\n        \"title\": \"Умная сумма\"\n      }\n    },\n    \"inputTitle\": \"Вход\",\n    \"numberExtraction\": \"Извлечение чисел\",\n    \"printRunningSum\": \"Печать текущей суммы\",\n    \"printRunningSumDescription\": \"Отобразить сумму по мере ее расчета шаг за шагом.\",\n    \"resultTitle\": \"Общий\",\n    \"runningSum\": \"Текущая сумма\",\n    \"shortDescription\": \"Вычислить сумму чисел\",\n    \"title\": \"Сумма\",\n    \"toolInfo\": {\n      \"description\": \"Это онлайн-браузерная утилита для вычисления суммы набора чисел. Вы можете вводить числа, разделяя их запятой, пробелом или любым другим символом, включая перенос строки. Вы также можете просто вставить фрагмент текстовых данных, содержащий числовые значения, которые нужно просуммировать, и утилита извлечет их и вычислит сумму.\",\n      \"title\": \"Что такое калькулятор суммы чисел?\"\n    }\n  },\n  \"voltageDropInWire\": {\n    \"description\": \"Рассчитывает напряжение в обоих направлениях и потери мощности в двухпроводном кабеле.\",\n    \"longDescription\": \"Этот калькулятор помогает определить падение напряжения и потери мощности в двухжильном электрическом кабеле. Он учитывает длину кабеля, площадь поперечного сечения проводов, удельное сопротивление материала и силу тока. Инструмент рассчитывает падение напряжения в обоих направлениях, полное сопротивление кабеля и мощность, рассеиваемую в виде тепла. Это особенно полезно для инженеров-электриков, электриков и любителей при проектировании электрических систем, чтобы гарантировать, что уровень напряжения на нагрузке будет в допустимых пределах.\",\n    \"shortDescription\": \"Рассчитать падение напряжения и потерю мощности в электрических кабелях на основе длины, материала и тока.\",\n    \"title\": \"Падение напряжения в кабеле в обоих направлениях\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/pdf.json",
    "content": "{\n  \"compressPdf\": {\n    \"compressedFileSize\": \"Размер сжатого файла\",\n    \"compressingPdf\": \"Сжатие PDF-файла...\",\n    \"compressionLevel\": \"Уровень сжатия\",\n    \"compressionSettings\": \"Настройки сжатия\",\n    \"description\": \"Уменьшите размер PDF-файла, сохранив качество, с помощью Ghostscript\",\n    \"errorCompressingPdf\": \"Не удалось сжать PDF: {{error}}\",\n    \"errorReadingPdf\": \"Не удалось прочитать PDF-файл. Убедитесь, что это корректный PDF-файл.\",\n    \"fileSize\": \"Исходный размер файла\",\n    \"highCompression\": \"Высокая степень сжатия\",\n    \"highCompressionDescription\": \"Максимальное уменьшение размера файла с некоторой потерей качества\",\n    \"inputTitle\": \"Входной PDF-файл\",\n    \"longDescription\": \"Сжимайте PDF-файлы безопасно в браузере с помощью Ghostscript. Ваши файлы никогда не покинут ваше устройство, обеспечивая полную конфиденциальность и уменьшая размер файлов для отправки по электронной почте, загрузки на веб-сайты или экономии места на диске. Работает на технологии WebAssembly.\",\n    \"lowCompression\": \"Низкая компрессия\",\n    \"lowCompressionDescription\": \"Немного уменьшить размер файла с минимальной потерей качества\",\n    \"mediumCompression\": \"Средняя компрессия\",\n    \"mediumCompressionDescription\": \"Баланс между размером файла и качеством\",\n    \"pages\": \"Количество страниц\",\n    \"resultTitle\": \"Сжатый PDF-файл\",\n    \"shortDescription\": \"Безопасное сжатие PDF-файлов в вашем браузере\",\n    \"title\": \"Сжать PDF\"\n  },\n  \"editor\": {\n    \"description\": \"Расширенный редактор PDF с функциями аннотирования, заполнения форм, выделения текста и экспорта. Редактируйте PDF-файлы прямо в браузере с помощью профессиональных инструментов, включая вставку текста, рисование, выделение текста, подписание и заполнение форм.\",\n    \"shortDescription\": \"Редактируйте PDF-файлы с помощью расширенных инструментов аннотирования, подписания и редактирования.\",\n    \"title\": \"PDF-редактор\"\n  },\n  \"merge\": {\n    \"inputTitle\": \"Входной PDF-файл\",\n    \"loadingText\": \"Извлечение страниц\",\n    \"resultTitle\": \"Вывести объединенный PDF-файл\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет объединить несколько PDF-файлов в один документ. Чтобы воспользоваться инструментом, просто загрузите PDF-файлы, которые вы хотите объединить. Инструмент объединит все страницы исходных файлов в один PDF-документ.\",\n      \"title\": \"Как использовать инструмент объединения PDF-файлов?\"\n    }\n  },\n  \"mergePdf\": {\n    \"description\": \"Объедините несколько PDF-файлов в один документ.\",\n    \"inputTitle\": \"Входные PDF-файлы\",\n    \"mergingPdfs\": \"Объединение PDF-файлов\",\n    \"pdfOptions\": \"Параметры PDF-файла\",\n    \"resultTitle\": \"Объединенный PDF-файл\",\n    \"shortDescription\": \"Объединить несколько PDF-файлов в один документ\",\n    \"sortByFileName\": \"Сортировать по имени файла\",\n    \"sortByFileNameDescription\": \"Сортировать PDF-файлы в алфавитном порядке по имени файла\",\n    \"sortByUploadOrder\": \"Сортировать по порядку загрузки\",\n    \"sortByUploadOrderDescription\": \"Сохраняйте PDF-файлы в том порядке, в котором они были загружены.\",\n    \"title\": \"Объединить PDF\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет объединить несколько PDF-файлов в один документ. Вы можете выбрать способ сортировки PDF-файлов, и инструмент объединит их в указанном порядке.\",\n      \"title\": \"Объединить PDF-файлы\"\n    }\n  },\n  \"pdfToEpub\": {\n    \"description\": \"Преобразуйте PDF-документы в файлы EPUB для лучшей совместимости с электронными книгами.\",\n    \"shortDescription\": \"Конвертировать PDF-файлы в формат EPUB\",\n    \"title\": \"PDF в EPUB\"\n  },\n  \"pdfToPng\": {\n    \"description\": \"Преобразуйте PDF-документы в панели PNG.\",\n    \"longDescription\": \"Загрузите PDF-файл и преобразуйте каждую страницу в высококачественное изображение PNG прямо в браузере. Этот инструмент идеально подходит для извлечения визуального контента или публикации отдельных страниц. Загрузка данных не требуется — всё работает локально.\",\n    \"shortDescription\": \"Конвертировать PDF в изображения PNG\",\n    \"title\": \"PDF в PNG\"\n  },\n  \"convertToPdf\": {\n    \"title\": \"Изображения в PDF\",\n    \"description\": \"Преобразовать различные форматы изображений (PNG, GIF, JPG, TIF, PSD, SVG, WEBP, HEIC, RAW) в PDF с возможностью масштабирования изображения и выбора ориентации страницы.\",\n    \"shortDescription\": \"Преобразовать изображения в PDF с управлением масштабом и ориентацией\"\n  },\n  \"protectPdf\": {\n    \"description\": \"Добавьте надежную защиту паролем для ваших PDF-файлов в браузере.\",\n    \"shortDescription\": \"Надежная защита паролем PDF-файлов\",\n    \"title\": \"Защитить PDF-файл\"\n  },\n  \"rotatePdf\": {\n    \"allPagesWillBeRotated\": \"Все {{count}} страницы будут ротироваться\",\n    \"angleOptions\": {\n      \"clockwise90\": \"90° по часовой стрелке\",\n      \"counterClockwise270\": \"270° (90° против часовой стрелки)\",\n      \"upsideDown180\": \"180° (вверх дном)\"\n    },\n    \"applyToAllPages\": \"Применить ко всем страницам\",\n    \"description\": \"Поворот страниц в PDF-документе.\",\n    \"inputTitle\": \"Входной PDF-файл\",\n    \"longDescription\": \"Измените ориентацию страниц PDF-файла, повернув их на 90, 180 или 270 градусов. Это полезно для исправления неправильно отсканированных документов или подготовки PDF-файлов к печати.\",\n    \"pageRangesDescription\": \"Введите номера страниц или диапазоны, разделенные запятыми (например, 1,3,5-7)\",\n    \"pageRangesPlaceholder\": \"например, 1,5-8\",\n    \"pagesWillBeRotated\": \"{{count}} страница{{count !== 1 ? 's' : ''}} будет вращаться\",\n    \"pdfPageCount\": \"PDF имеет {{count}} страница{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"Повернутый PDF-файл\",\n    \"rotatingPages\": \"Ротация страниц\",\n    \"rotationAngle\": \"Угол поворота\",\n    \"rotationSettings\": \"Настройки вращения\",\n    \"shortDescription\": \"Поворот страниц в PDF-документе\",\n    \"title\": \"Повернуть PDF\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет поворачивать страницы в PDF-документе. Вы можете повернуть все страницы или указать отдельные. Выберите угол поворота: 90° по часовой стрелке, 180° (вверх ногами) или 270° (на 90° против часовой стрелки). Чтобы повернуть отдельные страницы, снимите флажок «Применить ко всем страницам» и введите номера страниц или диапазоны через запятую (например, 1, 3, 5–7).\",\n      \"title\": \"Как использовать инструмент поворота PDF-файла\"\n    }\n  },\n  \"splitPdf\": {\n    \"description\": \"Извлечение определенных страниц из PDF-документа.\",\n    \"extractingPages\": \"Извлечение страниц\",\n    \"inputTitle\": \"Входной PDF-файл\",\n    \"pageExtractionPreview\": \"{{count}} страница{{count !== 1 ? 's' : ''}} будут извлечены\",\n    \"pageRangesDescription\": \"Введите номера страниц или диапазоны, разделенные запятыми (например, 1,3,5-7)\",\n    \"pageRangesPlaceholder\": \"например, 1,5-8\",\n    \"pageSelection\": \"Выбор страницы\",\n    \"pdfPageCount\": \"PDF имеет {{count}} страница{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"Извлеченный PDF-файл\",\n    \"shortDescription\": \"Извлечь определенные страницы из PDF-файла\",\n    \"title\": \"Разделить PDF\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет извлекать определённые страницы из PDF-документа. Вы можете указать отдельные страницы или диапазоны страниц для извлечения.\",\n      \"title\": \"Разделить PDF\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/string.json",
    "content": "{\n  \"base64\": {\n    \"decode\": \"Декодирование Base64\",\n    \"description\": \"Кодировать или декодировать текст с использованием кодировки Base64.\",\n    \"encode\": \"Кодирование Base64\",\n    \"inputTitle\": \"Входные данные\",\n    \"optionsTitle\": \"Параметры Base64\",\n    \"resultTitle\": \"Результат\",\n    \"shortDescription\": \"Кодировать или декодировать данные с помощью Base64.\",\n    \"title\": \"Кодировщик/декодер Base64\",\n    \"toolInfo\": {\n      \"description\": \"Base64 — это схема кодирования, которая представляет данные в формате ASCII-строки путём перевода их в систему счисления с основанием 64. Хотя её можно использовать для кодирования строк, она обычно применяется для кодирования двоичных данных для передачи по средам, предназначенным для работы с текстовыми данными.\",\n      \"title\": \"Что такое Base64?\"\n    }\n  },\n  \"censor\": {\n    \"description\": \"Утилита для цензурирования слов в тексте. Загрузите текст в форму ввода слева, укажите все нецензурные слова в параметрах, и вы мгновенно получите отцензурированный текст в области вывода.\\\", longDescription: 'С помощью этого онлайн-инструмента вы можете отцензурировать определённые слова в любом тексте. Вы можете указать список нежелательных слов (например, ругательств или секретных слов), и программа заменит их альтернативными словами и создаст безопасный для чтения текст. Слова можно указать в многострочном текстовом поле в параметрах, вводя по одному слову на строку.', keywords: ['text', 'censor', 'words', 'characters'], component: lazy(() => import('./index')), i18n: { name: 'string:censor.title', description: 'string:censor.description\",\n    \"shortDescription\": \"Быстро маскируйте плохие слова или заменяйте их другими словами.\",\n    \"title\": \"Текстовый цензор\"\n  },\n  \"createPalindrome\": {\n    \"description\": \"Самая простая в мире браузерная утилита для создания палиндромов из любого текста. Введите текст и мгновенно преобразуйте его в палиндром, который читается одинаково слева направо и справа налево. Идеально подходит для словесных игр, создания симметричных текстовых узоров или изучения лингвистических диковинок.\",\n    \"shortDescription\": \"Создайте текст, который одинаково читается как слева направо, так и слева направо.\",\n    \"title\": \"Создать палиндром\"\n  },\n  \"extractSubstring\": {\n    \"description\": \"Самая простая в мире браузерная утилита для извлечения подстрок из текста. Введите текст и укажите начальную и конечную позиции, чтобы извлечь нужную часть. Идеально подходит для обработки данных, анализа текста или извлечения определённого контента из больших текстовых блоков.\",\n    \"shortDescription\": \"Извлечь часть текста между указанными позициями\",\n    \"title\": \"Извлечь подстроку\"\n  },\n  \"hiddenCharacterDetector\": {\n    \"analysisOptions\": \"Параметры анализа\",\n    \"category\": \"Категория\",\n    \"description\": \"Обнаружение скрытых символов Unicode, особенно символов переопределения RTL, которые могут быть использованы в атаках.\",\n    \"foundChars\": \"Найденный {{count}} скрытые символы:\",\n    \"inputPlaceholder\": \"Введите текст для проверки на наличие скрытых символов...\",\n    \"inputTitle\": \"Текст для анализа\",\n    \"invisibleChar\": \"Невидимый персонаж\",\n    \"invisibleFound\": \"Найдены невидимые символы\",\n    \"longDescription\": \"Этот инструмент помогает обнаруживать скрытые символы Unicode в тексте, в частности, символы переопределения с написанием справа налево (RTL), которые могут использоваться для атак. Он может определять невидимые символы, символы нулевой ширины и другие потенциально вредоносные последовательности Unicode, которые могут быть скрыты в, казалось бы, безобидном тексте.\",\n    \"noHiddenChars\": \"Скрытых символов в тексте не обнаружено.\",\n    \"optionsDescription\": \"Настройте, какие типы скрытых символов следует обнаруживать и как отображать результаты.\",\n    \"position\": \"Позиция\",\n    \"rtlAlert\": \"⚠️ Обнаружены символы переопределения RTL! Этот текст может содержать скрытые вредоносные символы.\",\n    \"rtlFound\": \"Найдено переопределение RTL\",\n    \"rtlOverride\": \"Символ переопределения RTL\",\n    \"rtlWarning\": \"ВНИМАНИЕ: Обнаружены символы переопределения RTL! Это может быть использовано в атаках.\",\n    \"shortDescription\": \"Найти скрытые символы Unicode в тексте\",\n    \"summary\": \"Резюме анализа\",\n    \"title\": \"Детектор скрытых символов\",\n    \"totalChars\": \"Всего скрытых символов: {{count}}\",\n    \"unicode\": \"Юникод\",\n    \"zeroWidthChar\": \"Символ нулевой ширины\",\n    \"zeroWidthFound\": \"Найдены символы нулевой ширины\"\n  },\n  \"join\": {\n    \"blankLinesAndTrailingSpaces\": \"Пустые строки и конечные пробелы\",\n    \"deleteBlankDescription\": \"Удалите строки, не содержащие текстовых символов.\",\n    \"deleteBlankTitle\": \"Удалить пустые строки\",\n    \"deleteTrailingDescription\": \"Удалите пробелы и знаки табуляции в конце строк.\",\n    \"deleteTrailingTitle\": \"Удалить конечные пробелы\",\n    \"description\": \"Объединяйте фрагменты текста с помощью настраиваемых разделителей.\",\n    \"inputTitle\": \"Текстовые фрагменты\",\n    \"joinCharacterDescription\": \"Символ, соединяющий разрозненные фрагменты текста. (Пробел по умолчанию.)\",\n    \"joinCharacterPlaceholder\": \"Присоединиться к персонажу\",\n    \"resultTitle\": \"Объединенный текст\",\n    \"shortDescription\": \"Объединить текстовые элементы с указанным разделителем\",\n    \"textMergedOptions\": \"Параметры объединения текста\",\n    \"title\": \"Присоединиться к тексту\",\n    \"toolInfo\": {\n      \"description\": \"С помощью этого инструмента можно объединить части текста. Он берёт список текстовых значений, разделённых символами переноса строки, и объединяет их. Вы можете задать символ, который будет вставлен между частями объединённого текста. Кроме того, можно игнорировать все пустые строки и удалять пробелы и табуляции в конце всех строк. Текстуально!\",\n      \"title\": \"Что такое средство объединения текстов?\"\n    }\n  },\n  \"palindrome\": {\n    \"description\": \"Самая простая в мире браузерная утилита для проверки текста на палиндром. Мгновенно проверяет, одинаково ли читается текст слева направо и справа налево. Идеально подходит для решения словесных головоломок, лингвистического анализа и проверки симметричности текстовых шаблонов. Поддерживает различные разделители и определение палиндромов из нескольких слов.\",\n    \"shortDescription\": \"Проверьте, читается ли текст одинаково в прямом и обратном направлении.\",\n    \"title\": \"Палиндром\"\n  },\n  \"passwordGenerator\": {\n    \"avoidAmbiguous\": \"Избегайте неоднозначных символов (i, I, l, 0, O)\",\n    \"description\": \"Создавайте надёжные случайные пароли с настраиваемой длиной и типом символов. Выбирайте строчные, заглавные, цифры и специальные символы. Возможность исключить неоднозначные символы для лучшей читаемости.\",\n    \"includeLowercase\": \"Включать строчные буквы (a–z)\",\n    \"includeNumbers\": \"Включить цифры (0-9)\",\n    \"includeSymbols\": \"Включить специальные символы\",\n    \"includeUppercase\": \"Включайте заглавные буквы (A–Z)\",\n    \"lengthDesc\": \"Длина пароля\",\n    \"lengthPlaceholder\": \"например, 12\",\n    \"optionsTitle\": \"Параметры пароля\",\n    \"resultTitle\": \"Сгенерированный пароль\",\n    \"shortDescription\": \"Генерируйте безопасные случайные пароли с помощью пользовательских параметров\",\n    \"title\": \"Генератор паролей\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент генерирует надёжные случайные пароли на основе выбранных вами критериев. Вы можете настроить длину, включать или исключать различные типы символов, а также избегать неоднозначных символов для лучшей читаемости. Идеально подходит для создания надёжных паролей для учётных записей, приложений и любых других задач безопасности.\",\n      \"title\": \"О генераторе паролей\"\n    }\n  },\n  \"quote\": {\n    \"allowDoubleQuotation\": \"Разрешить двойные кавычки\",\n    \"description\": \"Добавляйте кавычки вокруг текста с помощью настраиваемых параметров.\",\n    \"inputTitle\": \"Введите текст\",\n    \"leftQuoteDescription\": \"Символ(ы) левой кавычки\",\n    \"processAsMultiLine\": \"Обрабатывать как многострочный текст\",\n    \"quoteEmptyLines\": \"Цитировать пустые строки\",\n    \"quoteOptions\": \"Варианты расценок\",\n    \"resultTitle\": \"Цитируемый текст\",\n    \"rightQuoteDescription\": \"Символ(ы) правой кавычки\",\n    \"shortDescription\": \"Добавляйте кавычки вокруг текста с использованием различных стилей\",\n    \"title\": \"Цитата из текста\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет добавлять кавычки к тексту. Вы можете выбирать различные символы кавычек, работать с многострочным текстом и управлять обработкой пустых строк. Он полезен для подготовки текста к программированию, форматирования данных или создания стилизованного текста.\",\n      \"title\": \"Цитата из текста\"\n    }\n  },\n  \"randomizeCase\": {\n    \"description\": \"Самая простая в мире браузерная утилита для случайного изменения регистра текста. Введите текст и мгновенно преобразуйте его, используя случайные заглавные и строчные буквы. Идеально подходит для создания уникальных текстовых эффектов, проверки чувствительности к регистру или генерации различных текстовых шаблонов.\",\n    \"shortDescription\": \"Случайный регистр букв в тексте\",\n    \"title\": \"Рандомизировать случай\"\n  },\n  \"removeDuplicateLines\": {\n    \"description\": \"Загрузите текст в форму ввода слева, и вы мгновенно получите текст без дубликатов строк в области вывода. Мощный, бесплатный и быстрый инструмент. Загрузите текстовые строки — получите уникальные текстовые строки.\",\n    \"shortDescription\": \"Быстро удалить все повторяющиеся строки из текста\",\n    \"title\": \"Удалить дубликаты строк\"\n  },\n  \"repeat\": {\n    \"delimiterDescription\": \"Разделитель для выходных копий.\",\n    \"delimiterPlaceholder\": \"Разделитель\",\n    \"description\": \"Повторяйте текст несколько раз с настраиваемыми разделителями.\",\n    \"inputTitle\": \"Введите текст\",\n    \"numberPlaceholder\": \"Число\",\n    \"repeatAmountDescription\": \"Количество повторений.\",\n    \"repetitionsDelimiter\": \"Разделитель повторений\",\n    \"resultTitle\": \"Повторяющийся текст\",\n    \"shortDescription\": \"Повторить текст несколько раз\",\n    \"textRepetitions\": \"Повторы текста\",\n    \"title\": \"Повторить текст\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет повторять заданный текст несколько раз с дополнительным разделителем.\",\n      \"title\": \"Повторить текст\"\n    }\n  },\n  \"reverse\": {\n    \"description\": \"Самая простая в мире браузерная утилита для переворота текста. Введите любой текст, и он мгновенно перевернётся, символ за символом. Идеально подходит для создания зеркального текста, анализа палиндромов и экспериментов с текстовыми шаблонами. Сохраняет пробелы и специальные символы при перевороте.\",\n    \"inputTitle\": \"Текст для переворота\",\n    \"processMultiLine\": \"Обработка многострочного текста\",\n    \"processMultiLineDescription\": \"Каждая строка будет перевернута независимо\",\n    \"resultTitle\": \"Перевернутый текст\",\n    \"reversalOptions\": \"Варианты отмены\",\n    \"shortDescription\": \"Перевернуть любой текст посимвольно\",\n    \"skipEmptyLines\": \"Пропускать пустые строки\",\n    \"skipEmptyLinesDescription\": \"Пустые строки будут удалены из вывода.\",\n    \"title\": \"Обеспечить регресс\",\n    \"trimWhitespace\": \"Обрезать пробелы\",\n    \"trimWhitespaceDescription\": \"Удалить начальные и конечные пробелы из каждой строки.\"\n  },\n  \"rot13\": {\n    \"description\": \"Кодировать или декодировать текст с помощью шифра ROT13.\",\n    \"inputTitle\": \"Введите текст\",\n    \"resultTitle\": \"Результат ROT13\",\n    \"shortDescription\": \"Кодировать или декодировать текст с помощью шифра ROT13.\",\n    \"title\": \"Кодер/декодер ROT13\",\n    \"toolInfo\": {\n      \"description\": \"ROT13 (поворот на 13 позиций) — это простой шифр замены букв, который заменяет букву на 13-ю букву после неё в алфавите. ROT13 — это особый случай шифра Цезаря, разработанного в Древнем Риме. Поскольку в английском алфавите 26 букв, ROT13 является своим собственным инверсией; то есть для отмены ROT13 применяется тот же алгоритм, поэтому одно и то же действие можно использовать для кодирования и декодирования.\",\n      \"title\": \"Что такое ROT13?\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"Повернуть символы в тексте на указанные позиции.\",\n    \"inputTitle\": \"Введите текст\",\n    \"processAsMultiLine\": \"Обрабатывать как многострочный текст (поворачивать каждую строку отдельно)\",\n    \"resultTitle\": \"Повернутый текст\",\n    \"rotateLeft\": \"Повернуть влево\",\n    \"rotateRight\": \"Повернуть вправо\",\n    \"rotationOptions\": \"Параметры вращения\",\n    \"shortDescription\": \"Сдвиг символов в тексте по позиции.\",\n    \"stepDescription\": \"Количество позиций для поворота\",\n    \"title\": \"Повернуть текст\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет поворачивать символы в строке на заданное количество позиций. Вы можете поворачивать символы влево или вправо, а также обрабатывать многострочный текст, поворачивая каждую строку отдельно. Поворот строк полезен для простых преобразований текста, создания шаблонов или реализации базовых методов шифрования.\",\n      \"title\": \"Вращение струны\"\n    }\n  },\n  \"split\": {\n    \"charAfterChunkDescription\": \"Символ после каждого фрагмента\",\n    \"charBeforeChunkDescription\": \"Символ перед каждым фрагментом\",\n    \"chunksDescription\": \"Количество фрагментов одинаковой длины на выходе.\",\n    \"chunksTitle\": \"Используйте несколько фрагментов\",\n    \"description\": \"Самая простая в мире браузерная утилита для разделения текста. Введите текст и укажите разделитель, чтобы разделить его на несколько частей. Идеально подходит для обработки данных, работы с текстом или извлечения определённого контента из больших текстовых блоков.\",\n    \"lengthDescription\": \"Количество символов, которые будут помещены в каждый выходной фрагмент.\",\n    \"lengthTitle\": \"Использовать длину для разделения\",\n    \"outputSeparatorDescription\": \"Символ, который будет вставлен между разделёнными фрагментами.\\n(По умолчанию это символ новой строки \\\"\\\\n\\\".)\",\n    \"outputSeparatorOptions\": \"Параметры разделителя вывода\",\n    \"regexDescription\": \"Регулярное выражение, которое будет использоваться для разбиения текста на части.\\n(По умолчанию несколько пробелов.)\",\n    \"regexTitle\": \"Используйте регулярное выражение для разделения\",\n    \"resultTitle\": \"Текстовые фрагменты\",\n    \"shortDescription\": \"Разделить текст на несколько частей с помощью разделителя\",\n    \"splitSeparatorOptions\": \"Параметры разделителя разделителей\",\n    \"symbolDescription\": \"Символ, который будет использоваться для разбиения текста на части.\\n(Пробел по умолчанию.)\",\n    \"symbolTitle\": \"Используйте символ для разделения\",\n    \"title\": \"Расколоть\"\n  },\n  \"statistic\": {\n    \"characterFrequencyAnalysis\": \"Анализ частоты символов\",\n    \"characterFrequencyAnalysisDescription\": \"Подсчитайте, как часто каждый символ встречается в тексте.\",\n    \"delimitersOptions\": \"Параметры разделителей\",\n    \"description\": \"Анализируйте текст и создавайте комплексную статистику.\",\n    \"includeEmptyLines\": \"Включить пустые строки\",\n    \"includeEmptyLinesDescription\": \"При подсчете строк учитывайте пустые строки.\",\n    \"inputTitle\": \"Введите текст\",\n    \"resultTitle\": \"Статистика текста\",\n    \"sentenceDelimitersDescription\": \"Введите пользовательские символы, используемые для разделения предложений на вашем языке (через запятую), или оставьте поле пустым, чтобы использовать значение по умолчанию.\",\n    \"sentenceDelimitersPlaceholder\": \"например, ., !, ?, ...\",\n    \"shortDescription\": \"Получите статистику о вашем тексте\",\n    \"statisticsOptions\": \"Параметры статистики\",\n    \"title\": \"Статистика текста\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет анализировать текст и генерировать комплексную статистику, включая количество символов, количество слов, количество строк, а также анализ частоты символов и слов.\",\n      \"title\": \"Что такое {{title}}?\"\n    },\n    \"wordDelimitersDescription\": \"Введите пользовательское регулярное выражение для подсчета слов или оставьте поле пустым для использования по умолчанию.\",\n    \"wordDelimitersPlaceholder\": \"напр. \\\\s.,;:!?\\\"«»()…\",\n    \"wordFrequencyAnalysis\": \"Анализ частоты слов\",\n    \"wordFrequencyAnalysisDescription\": \"Подсчитайте, как часто каждое слово встречается в тексте.\"\n  },\n  \"textReplacer\": {\n    \"description\": \"Замените текстовые шаблоны новым содержанием.\",\n    \"findPatternInText\": \"Найти этот шаблон в тексте\",\n    \"findPatternUsingRegexp\": \"Найдите шаблон с помощью регулярного выражения\",\n    \"inputTitle\": \"Текст для замены\",\n    \"newTextPlaceholder\": \"Новый текст\",\n    \"regexpDescription\": \"Введите регулярное выражение, которое вы хотите заменить.\",\n    \"replacePatternDescription\": \"Введите шаблон, который будет использоваться для замены.\",\n    \"replaceText\": \"Заменить текст\",\n    \"resultTitle\": \"Текст с заменами\",\n    \"searchPatternDescription\": \"Введите текстовый шаблон, который вы хотите заменить.\",\n    \"searchText\": \"Поиск текста\",\n    \"shortDescription\": \"Быстро заменяйте текст в вашем контенте\",\n    \"title\": \"Заменитель текста\",\n    \"toolInfo\": {\n      \"description\": \"Легко заменяйте текст в вашем контенте с помощью этого простого браузерного инструмента. Просто введите текст, укажите текст, который хотите заменить, и значение для замены, и мгновенно получите обновлённую версию.\",\n      \"title\": \"Заменитель текста\"\n    }\n  },\n  \"toMorse\": {\n    \"dashSymbolDescription\": \"Символ, который будет соответствовать тире в азбуке Морзе.\",\n    \"description\": \"Преобразовать текст в азбуку Морзе.\",\n    \"dotSymbolDescription\": \"Символ, который будет соответствовать точке в азбуке Морзе.\",\n    \"longSignal\": \"Длинный сигнал\",\n    \"resultTitle\": \"азбука Морзе\",\n    \"shortDescription\": \"Быстрое кодирование текста в азбуку Морзе\",\n    \"shortSignal\": \"Короткий сигнал\",\n    \"title\": \"Строка в азбуке Морзе\"\n  },\n  \"truncate\": {\n    \"addTruncationIndicator\": \"Добавить индикатор усечения\",\n    \"charactersPlaceholder\": \"Персонажи\",\n    \"description\": \"Сократить текст до указанной длины.\",\n    \"indicatorDescription\": \"Символы, добавляемые в конец (или начало) текста. Примечание: они учитываются при подсчёте длины.\",\n    \"inputTitle\": \"Введите текст\",\n    \"leftSideDescription\": \"Удалить символы из начала текста.\",\n    \"leftSideTruncation\": \"Усечение слева\",\n    \"lengthAndLines\": \"Длина и линии\",\n    \"lineByLineDescription\": \"Обрезайте каждую строку отдельно.\",\n    \"lineByLineTruncating\": \"Построчное усечение\",\n    \"maxLengthDescription\": \"Количество символов, которое нужно оставить в тексте.\",\n    \"numberPlaceholder\": \"Число\",\n    \"resultTitle\": \"Усеченный текст\",\n    \"rightSideDescription\": \"Удалить символы из конца текста.\",\n    \"rightSideTruncation\": \"Правостороннее усечение\",\n    \"shortDescription\": \"Обрезать текст до указанной длины\",\n    \"suffixAndAffix\": \"Суффикс и аффикс\",\n    \"title\": \"Обрезать текст\",\n    \"toolInfo\": {\n      \"description\": \"Загрузите текст в форму ввода слева, и справа вы автоматически получите обрезанный текст.\",\n      \"title\": \"Обрезать текст\"\n    },\n    \"truncationSide\": \"Сторона усечения\"\n  },\n  \"uppercase\": {\n    \"description\": \"Преобразовать текст в заглавные буквы.\",\n    \"inputTitle\": \"Введите текст\",\n    \"resultTitle\": \"Текст заглавными буквами\",\n    \"shortDescription\": \"Преобразовать текст в верхний регистр\",\n    \"title\": \"Преобразовать в верхний регистр\"\n  },\n  \"urlDecode\": {\n    \"inputTitle\": \"Входная строка (с URL-экранированием)\",\n    \"resultTitle\": \"Выходная строка\",\n    \"toolInfo\": {\n      \"description\": \"Загрузите вашу строку, и она будет автоматически расэкранирована.\",\n      \"longDescription\": \"Этот инструмент декодирует URL-строку, ранее закодированную в URL-формате. URL-декодирование — это операция, обратная URL-кодированию. Все символы, закодированные с помощью процентов, декодируются в понятные вам символы. Наиболее известные значения, закодированные с помощью процентов, — это %20 (пробел), %3a (двоеточие), %2f (косая черта) и %3f (вопросительный знак). Две цифры после знака процента — это шестнадцатеричные коды символов.\",\n      \"shortDescription\": \"Быстрое расэкранирование строки URL.\",\n      \"title\": \"Декодер URL-адресов строк\"\n    }\n  },\n  \"urlEncode\": {\n    \"encodingOption\": {\n      \"nonSpecialCharDescription\": \"Если выбрано, все символы во входной строке будут преобразованы в URL-кодировку (а не только специальную).\",\n      \"nonSpecialCharPlaceholder\": \"Кодировать неспециальные символы\",\n      \"title\": \"Параметры кодирования\"\n    },\n    \"inputTitle\": \"Входная строка\",\n    \"resultTitle\": \"Строка, экранированная URL-адресом\",\n    \"toolInfo\": {\n      \"description\": \"Загрузите вашу строку, и она будет автоматически экранирована URL.\",\n      \"longDescription\": \"Этот инструмент кодирует строку в формате URL. Специальные символы URL преобразуются в кодировку процента. Такая кодировка называется процентной, поскольку числовое значение каждого символа преобразуется в знак процента, за которым следует двузначное шестнадцатеричное значение. Шестнадцатеричные значения определяются на основе кодовой точки символа. Например, пробел преобразуется в %20, двоеточие — в %3a, косая черта — в %2f. Символы, не являющиеся специальными, остаются без изменений. Если вам также нужно преобразовать неспециальные символы в кодировку процента, мы добавили дополнительную опцию, позволяющую это сделать. Выберите опцию encode-non-special-chars, чтобы включить эту функцию.\",\n      \"shortDescription\": \"Быстрое экранирование строки URL.\",\n      \"title\": \"Строковый URL-кодер\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/time.json",
    "content": "{\n  \"checkLeapYears\": {\n    \"description\": \"Проверьте, является ли год високосным, и получите информацию о високосном годе.\",\n    \"exampleDescription\": \"Одна из наших подруг родилась в високосный год, 29 февраля, и, как следствие, у неё день рождения только раз в 4 года. Поскольку мы никогда не помним точно, когда у неё день рождения, мы используем нашу программу для создания списка напоминаний о предстоящих високосных годах. Чтобы создать список её следующих дней рождения, мы загружаем в поле ввода последовательность лет с 2025 по 2040 и получаем статус каждого года. Если программа сообщает, что год високосный, то мы знаем, что нас пригласят на день рождения 29 февраля.\",\n    \"exampleTitle\": \"Найти дни рождения 29 февраля\",\n    \"inputTitle\": \"Введите год\",\n    \"resultTitle\": \"Результат високосного года\",\n    \"shortDescription\": \"Проверьте, является ли год високосным\",\n    \"title\": \"Проверьте високосные годы\",\n    \"toolInfo\": {\n      \"description\": \"Високосный год — это год, содержащий один дополнительный день (29 февраля) для синхронизации календарного года с астрономическим годом. Високосные годы происходят каждые 4 года, за исключением тех, которые делятся на 100, но не делятся на 400.\",\n      \"title\": \"Что такое високосный год?\"\n    }\n  },\n  \"convertDaysToHours\": {\n    \"addHoursName\": \"Добавить часы Имя\",\n    \"addHoursNameDescription\": \"Добавить строку часов к выходным значениям\",\n    \"description\": \"Конвертируйте дни в часы с помощью настраиваемых параметров.\",\n    \"hoursName\": \"Часы Имя\",\n    \"shortDescription\": \"Конвертировать дни в часы\",\n    \"title\": \"Конвертировать дни в часы\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет преобразовывать дни в часы. Вы можете ввести дни в виде чисел или единиц измерения, и инструмент преобразует их в часы. Вы также можете добавить суффикс «часы» к выходным значениям.\",\n      \"title\": \"Конвертировать дни в часы\"\n    }\n  },\n  \"convertHoursToDays\": {\n    \"addDaysName\": \"Добавить название дня\",\n    \"addDaysNameDescription\": \"Добавить строку days к выходным значениям\",\n    \"daysName\": \"Имя дня\",\n    \"description\": \"Конвертируйте часы в дни с помощью настраиваемых параметров.\",\n    \"shortDescription\": \"Конвертировать часы в дни\",\n    \"title\": \"Конвертировать часы в дни\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет конвертировать часы в дни. Вы можете ввести часы в виде чисел или единиц измерения, и инструмент конвертирует их в дни. Вы также можете добавить суффикс «дни» к выходным значениям.\",\n      \"title\": \"Конвертировать часы в дни\"\n    }\n  },\n  \"convertSecondsToTime\": {\n    \"addPadding\": \"Добавить отступ\",\n    \"addPaddingDescription\": \"Добавьте нули к часам, минутам и секундам.\",\n    \"description\": \"Перевод секунд в удобный для чтения формат времени (часы:минуты:секунды). Введите количество секунд, чтобы получить отформатированное время.\",\n    \"shortDescription\": \"Преобразовать секунды в формат времени\",\n    \"timePadding\": \"Заполнение времени\",\n    \"title\": \"Перевести секунды во время\",\n    \"toolInfo\": {\n      \"title\": \"Что такое {{title}}?\"\n    }\n  },\n  \"convertTimeToSeconds\": {\n    \"description\": \"Преобразовать форматированное время (ЧЧ:ММ:СС) в секунды.\",\n    \"inputTitle\": \"Время ввода\",\n    \"resultTitle\": \"Секунды\",\n    \"shortDescription\": \"Конвертировать формат времени в секунды\",\n    \"title\": \"Перевести время в секунды\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет преобразовывать форматированные строки времени (ЧЧ:ММ:СС) в секунды. Он полезен для расчёта длительностей и временных интервалов.\",\n      \"title\": \"Перевести время в секунды\"\n    }\n  },\n  \"convertUnixToDate\": {\n    \"addUtcLabel\": \"Добавить суффикс «UTC»\",\n    \"addUtcLabelDescription\": \"Отображать «UTC» после преобразованной даты (только для режима UTC)\",\n    \"description\": \"Преобразовать временную метку Unix в удобочитаемую дату.\",\n    \"outputOptions\": \"Параметры вывода\",\n    \"shortDescription\": \"Преобразовать временную метку Unix в дату\",\n    \"title\": \"Конвертировать Unix в дату\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент преобразует временную метку Unix (в секундах) в удобный для восприятия формат даты (например, ГГГГ-ММ-ДД ЧЧ:ММ:СС). Он поддерживает как локальный формат, так и формат UTC, что делает его полезным для быстрой интерпретации временных меток из журналов, API или систем, использующих время Unix.\",\n      \"title\": \"Конвертировать Unix в дату\"\n    },\n    \"useLocalTime\": \"Использовать местное время\",\n    \"useLocalTimeDescription\": \"Показывать преобразованную дату в вашем местном часовом поясе вместо UTC\",\n    \"withLabel\": \"Параметры\"\n  },\n  \"crontabGuru\": {\n    \"description\": \"Генерируйте и анализируйте выражения cron. Создавайте расписания cron для автоматизированных задач и системных заданий.\",\n    \"shortDescription\": \"Генерация и понимание выражений cron\",\n    \"title\": \"Crontab Гуру\"\n  },\n  \"timeBetweenDates\": {\n    \"description\": \"Рассчитайте разницу во времени между двумя датами. Получите точную продолжительность в днях, часах, минутах и секундах.\",\n    \"endDate\": \"Дата окончания\",\n    \"endDateTime\": \"Дата и время окончания\",\n    \"endTime\": \"Конец Времени\",\n    \"endTimezone\": \"Конечный часовой пояс\",\n    \"shortDescription\": \"Рассчитать время между двумя датами\",\n    \"startDate\": \"Дата начала\",\n    \"startDateTime\": \"Дата и время начала\",\n    \"startTime\": \"Время начала\",\n    \"startTimezone\": \"Начальный часовой пояс\",\n    \"title\": \"Время между датами\",\n    \"toolInfo\": {\n      \"description\": \"Рассчитайте точную разницу во времени между двумя датами и временем с поддержкой разных часовых поясов. Этот инструмент предоставляет подробную информацию о разнице во времени в различных единицах (годы, месяцы, дни, часы, минуты и секунды).\",\n      \"title\": \"Калькулятор времени между датами\"\n    }\n  },\n  \"truncateClockTime\": {\n    \"description\": \"Округлите время до ближайшего часа, минуты или заданного вами интервала.\",\n    \"printDroppedComponents\": \"Печать удаленных компонентов\",\n    \"shortDescription\": \"Усечение времени до заданной точности\",\n    \"timePadding\": \"Заполнение времени\",\n    \"title\": \"Усечение времени на часах\",\n    \"toolInfo\": {\n      \"title\": \"Что такое {{title}}?\"\n    },\n    \"truncateMinutesAndSeconds\": \"Усечение минут и секунд\",\n    \"truncateMinutesAndSecondsDescription\": \"Отбросьте обе составляющие — минуты и секунды — из каждого времени.\",\n    \"truncateOnlySeconds\": \"Сократить только секунды\",\n    \"truncateOnlySecondsDescription\": \"Отбросьте секундную составляющую из каждого показания часов.\",\n    \"truncationSide\": \"Сторона усечения\",\n    \"useZeroPadding\": \"Использовать нулевое заполнение\",\n    \"zeroPaddingDescription\": \"Сделайте так, чтобы все компоненты времени всегда были двузначными.\",\n    \"zeroPrintDescription\": \"Отобразить отброшенные части как нулевые значения «00».\",\n    \"zeroPrintTruncatedParts\": \"Усеченные детали с нулевой печатью\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/translation.json",
    "content": "{\n  \"audio\": {\n    \"changeSpeed\": {\n      \"description\": \"Измените скорость воспроизведения аудиофайлов. Ускорьте или замедлите звук, сохраняя высоту звука.\",\n      \"name\": \"Изменить скорость звука\",\n      \"shortDescription\": \"Изменить скорость аудиофайлов\"\n    },\n    \"extractAudio\": {\n      \"description\": \"Извлеките звуковую дорожку из видеофайла и сохраните ее как отдельный аудиофайл в выбранном вами формате (AAC, MP3, WAV).\",\n      \"name\": \"Извлечь аудио\",\n      \"shortDescription\": \"Извлекайте аудио из видеофайлов (MP4, MOV и т. д.) в форматы AAC, MP3 или WAV.\"\n    }\n  },\n  \"baseFileInput\": {\n    \"copyFailed\": \"Не удалось скопировать: {{error}}\",\n    \"dropFileHere\": \"Оставьте свой {{type}} здесь\",\n    \"fileCopied\": \"Файл скопирован\",\n    \"selectFileDescription\": \"Нажмите здесь, чтобы выбрать {{type}} с вашего устройства нажмите Ctrl+V, чтобы использовать {{type}} из буфера обмена или перетащите файл с рабочего стола\"\n  },\n  \"categories\": {\n    \"audio\": {\n      \"description\": \"Инструменты для работы со звуком — извлечение звука из видео, регулировка скорости звука, объединение нескольких аудиофайлов и многое другое.\",\n      \"title\": \"Аудио инструменты\"\n    },\n    \"csv\": {\n      \"description\": \"Инструменты для работы с CSV-файлами — конвертируйте CSV в различные форматы, обрабатывайте CSV-данные, проверяйте структуру CSV и эффективно обрабатывайте CSV-файлы.\",\n      \"title\": \"CSV-инструменты\"\n    },\n    \"gif\": {\n      \"description\": \"Инструменты для работы с GIF-анимацией — создание прозрачных GIF-файлов, извлечение GIF-кадров, добавление текста в GIF-файлы, обрезка, поворот, обратное отображение GIF-файлов и многое другое.\",\n      \"title\": \"GIF-инструменты\"\n    },\n    \"image-generic\": {\n      \"description\": \"Инструменты для работы с изображениями — сжатие, изменение размера, обрезка, конвертация в JPG, поворот, удаление фона и многое другое.\",\n      \"title\": \"Инструменты для работы с изображениями\"\n    },\n    \"json\": {\n      \"description\": \"Инструменты для работы со структурой данных JSON — упрощайте и минимизируйте объекты JSON, выравнивайте массивы JSON, преобразуйте значения JSON в строки, анализируйте данные и многое другое.\",\n      \"title\": \"JSON-инструменты\"\n    },\n    \"list\": {\n      \"description\": \"Инструменты для работы со списками — сортировка, обратный порядок, рандомизация списков, поиск уникальных и повторяющихся элементов списка, изменение разделителей элементов списка и многое другое.\",\n      \"title\": \"Инструменты для работы со списками\"\n    },\n    \"number\": {\n      \"description\": \"Инструменты для работы с числами — генерация числовых последовательностей, преобразование чисел в слова и слов в числа, сортировка, округление, разложение чисел на множители и многое другое.\",\n      \"title\": \"Числовые инструменты\"\n    },\n    \"pdf\": {\n      \"description\": \"Инструменты для работы с PDF-файлами — извлечение текста из PDF-файлов, конвертация PDF-файлов в другие форматы, манипулирование PDF-файлами и многое другое.\",\n      \"title\": \"PDF-инструменты\"\n    },\n    \"png\": {\n      \"description\": \"Инструменты для работы с изображениями PNG — конвертируйте PNG в JPG, создавайте прозрачные PNG, изменяйте цвета PNG, обрезайте, поворачивайте, изменяйте размер PNG и многое другое.\",\n      \"title\": \"PNG-инструменты\"\n    },\n    \"seeAll\": \"Посмотреть {{title}}\",\n    \"string\": {\n      \"description\": \"Инструменты для работы с текстом — преобразование текста в изображения, поиск и замена текста, разделение текста на фрагменты, объединение текстовых строк, повтор текста и многое другое.\",\n      \"title\": \"Текстовые инструменты\"\n    },\n    \"time\": {\n      \"description\": \"Инструменты для работы со временем и датой — расчет разницы во времени, преобразование часовых поясов, форматирование дат, создание последовательностей дат и многое другое.\",\n      \"title\": \"Инструменты для работы со временем\"\n    },\n    \"try\": \"Запустить {{title}}\",\n    \"video\": {\n      \"description\": \"Инструменты для работы с видео — извлечение кадров из видео, создание GIF-файлов из видео, конвертация видео в различные форматы и многое другое.\",\n      \"title\": \"Видео инструменты\"\n    },\n    \"xml\": {\n      \"description\": \"Инструменты для работы с XML-структурами данных — просмотрщик, улучшитель, валидатор и многое другое\",\n      \"title\": \"XML-инструменты\"\n    }\n  },\n  \"csv\": {\n    \"findIncompleteCsvRecords\": {\n      \"description\": \"Просто загрузите CSV-файл в форму ниже, и этот инструмент автоматически проверит наличие всех пропущенных значений в строках и столбцах. В настройках инструмента можно настроить формат входного файла (указать разделитель, символ кавычки и символ комментария). Кроме того, можно включить проверку на наличие пустых значений, пропускать пустые строки и установить ограничение на количество сообщений об ошибках в выходных данных.\",\n      \"name\": \"Найти неполные записи CSV\",\n      \"shortDescription\": \"Быстро находите строки и столбцы в CSV-файле, в которых отсутствуют значения.\"\n    }\n  },\n  \"hero\": {\n    \"brand\": \"OmniTools\",\n    \"description\": \"Повысьте свою производительность с OmniTools — лучшим набором инструментов для быстрого решения задач! Получите доступ к тысячам удобных утилит для редактирования изображений, текста, списков и данных — и всё это прямо в браузере.\",\n    \"examples\": {\n      \"calculateNumberSum\": \"Вычислить сумму чисел\",\n      \"changeGifSpeed\": \"Изменить скорость GIF-анимации\",\n      \"compressPng\": \"Сжать PNG\",\n      \"createTransparentImage\": \"Создать прозрачное изображение\",\n      \"prettifyJson\": \"Отформатировать JSON\",\n      \"sortList\": \"Сортировать список\",\n      \"splitPdf\": \"Разделить PDF\",\n      \"splitText\": \"Разделить текст\",\n      \"trimVideo\": \"Обрезать видео\"\n    },\n    \"searchPlaceholder\": \"Найти инструмент…\",\n    \"title\": \"Выполняйте задачи быстро с помощью\"\n  },\n  \"inputFooter\": {\n    \"clear\": \"Прозрачный\",\n    \"copyToClipboard\": \"Копировать в буфер обмена\",\n    \"importFromFile\": \"Импорт из файла\"\n  },\n  \"list\": {\n    \"group\": {\n      \"description\": \"Самая простая в мире браузерная утилита для группировки элементов списка. Введите список и укажите критерии группировки, чтобы организовать элементы в логические группы. Идеально подходит для категоризации данных, организации информации и создания структурированных списков. Поддерживает настраиваемые разделители и различные варианты группировки.\",\n      \"name\": \"Группа\",\n      \"shortDescription\": \"Группировать элементы списка по общим свойствам\"\n    },\n    \"reverse\": {\n      \"description\": \"Это очень простое браузерное приложение, которое выводит все элементы списка в обратном порядке. Элементы ввода можно разделять любым символом, и вы также можете изменить разделитель для элементов перевёрнутого списка.\",\n      \"name\": \"Вывести в обратном порядке\",\n      \"shortDescription\": \"Быстро перевернуть список\"\n    },\n    \"sort\": {\n      \"description\": \"Это очень простое браузерное приложение, которое сортирует элементы списка в порядке возрастания или убывания. Вы можете сортировать элементы по алфавиту, по номерам или по длине. Вы также можете удалять повторяющиеся и пустые элементы, а также обрезать отдельные элементы с пробелами вокруг них. Для разделения элементов входного списка можно использовать любой символ-разделитель или регулярное выражение. Кроме того, вы можете создать новый разделитель для отсортированного выходного списка.\",\n      \"name\": \"Сортировать\",\n      \"shortDescription\": \"Быстрая сортировка списка\"\n    }\n  },\n  \"navbar\": {\n    \"buyMeACoffee\": \"Купи мне кофе\",\n    \"hireMe\": \"Нанять меня\",\n    \"home\": \"Дом\",\n    \"tools\": \"Инструменты\"\n  },\n  \"number\": {\n    \"generate\": {\n      \"description\": \"Быстро вычисляйте список целых чисел в браузере. Чтобы получить список, просто укажите первое целое число, измените значение и общее количество в параметрах ниже, и эта утилита сгенерирует нужное количество целых чисел.\",\n      \"name\": \"Генерировать числа\",\n      \"shortDescription\": \"Быстро вычислите список целых чисел в вашем браузере\"\n    },\n    \"sum\": {\n      \"description\": \"Это очень простое браузерное приложение для сложения чисел. Вводимые числа можно разделять любым символом, а также можно изменить разделитель суммируемых чисел.\",\n      \"name\": \"Сумма чисел\",\n      \"shortDescription\": \"Быстро суммировать список чисел\"\n    }\n  },\n  \"numericInputWithUnit\": {\n    \"unit\": \"Единица\"\n  },\n  \"pdf\": {\n    \"compressPdf\": {\n      \"description\": \"Уменьшите размер PDF-файла, сохранив качество, с помощью Ghostscript\",\n      \"name\": \"Сжать PDF\",\n      \"shortDescription\": \"Безопасное сжатие PDF-файлов в вашем браузере\"\n    },\n    \"mergePdf\": {\n      \"description\": \"Объедините несколько PDF-файлов в один документ.\",\n      \"name\": \"Объединить PDF\",\n      \"shortDescription\": \"Объединить несколько PDF-файлов в один документ\"\n    },\n    \"pdfToEpub\": {\n      \"description\": \"Преобразуйте PDF-документы в файлы EPUB для лучшей совместимости с электронными книгами.\",\n      \"name\": \"PDF в EPUB\",\n      \"shortDescription\": \"Конвертировать PDF-файлы в формат EPUB\"\n    },\n    \"protectPdf\": {\n      \"description\": \"Добавьте надежную защиту паролем для ваших PDF-файлов в браузере.\",\n      \"name\": \"Защитить PDF-файл\",\n      \"shortDescription\": \"Надежная защита паролем PDF-файлов\"\n    },\n    \"splitPdf\": {\n      \"description\": \"Извлечение определенных страниц из PDF-файла с использованием номеров или диапазонов страниц (например, 1,5-8)\",\n      \"name\": \"Разделить PDF\",\n      \"shortDescription\": \"Извлечь определенные страницы из PDF-файла\"\n    }\n  },\n  \"resultFooter\": {\n    \"copy\": \"Копировать в буфер обмена\",\n    \"download\": \"Скачать\"\n  },\n  \"string\": {\n    \"createPalindrome\": {\n      \"description\": \"Самая простая в мире браузерная утилита для создания палиндромов из любого текста. Введите текст и мгновенно преобразуйте его в палиндром, который читается одинаково слева направо и справа налево. Идеально подходит для словесных игр, создания симметричных текстовых узоров или изучения лингвистических диковинок.\",\n      \"name\": \"Создать палиндром\",\n      \"shortDescription\": \"Создайте текст, который одинаково читается как слева направо, так и слева направо.\"\n    },\n    \"palindrome\": {\n      \"description\": \"Самая простая в мире браузерная утилита для проверки текста на палиндром. Мгновенно проверяет, одинаково ли читается текст слева направо и справа налево. Идеально подходит для решения словесных головоломок, лингвистического анализа и проверки симметричности текстовых шаблонов. Поддерживает различные разделители и определение палиндромов из нескольких слов.\",\n      \"name\": \"Палиндром\",\n      \"shortDescription\": \"Проверьте, читается ли текст одинаково в прямом и обратном направлении.\"\n    },\n    \"repeat\": {\n      \"description\": \"Этот инструмент позволяет повторять заданный текст несколько раз с дополнительным разделителем.\",\n      \"name\": \"Повторить текст\",\n      \"shortDescription\": \"Повторить текст несколько раз\"\n    },\n    \"reverse\": {\n      \"description\": \"Самая простая в мире браузерная утилита для переворота текста. Введите любой текст, и он мгновенно перевернётся, символ за символом. Идеально подходит для создания зеркального текста, анализа палиндромов и экспериментов с текстовыми шаблонами. Сохраняет пробелы и специальные символы при перевороте.\",\n      \"name\": \"Обеспечить регресс\",\n      \"shortDescription\": \"Перевернуть любой текст посимвольно\"\n    },\n    \"toMorse\": {\n      \"description\": \"Самая простая в мире браузерная утилита для преобразования текста в код Морзе. Загрузите текст в форму ввода слева, и вы мгновенно получите код Морзе в области вывода. Мощная, бесплатная и быстрая. Загрузите текст — получите код Морзе.\",\n      \"name\": \"Строка в азбуке Морзе\",\n      \"shortDescription\": \"Быстрое кодирование текста в азбуку Морзе\"\n    },\n    \"uppercase\": {\n      \"description\": \"Самая простая в мире браузерная утилита для преобразования текста в верхний регистр. Просто введите текст, и он будет автоматически преобразован в заглавные буквы. Идеально подходит для создания заголовков, выделения текста или стандартизации форматирования текста. Поддерживает различные текстовые форматы и сохраняет специальные символы.\",\n      \"name\": \"Заглавные буквы\",\n      \"shortDescription\": \"Преобразовать текст в заглавные буквы\"\n    }\n  },\n  \"toolExamples\": {\n    \"subtitle\": \"Нажмите, чтобы попробовать!\",\n    \"title\": \"{{title}} Примеры\"\n  },\n  \"toolFileResult\": {\n    \"copied\": \"Файл скопирован\",\n    \"copyFailed\": \"Не удалось скопировать: {{error}}\",\n    \"loading\": \"Загрузка... Это может занять некоторое время.\",\n    \"result\": \"Результат\"\n  },\n  \"toolHeader\": {\n    \"seeExamples\": \"См. примеры\"\n  },\n  \"toolLayout\": {\n    \"allToolsTitle\": \"Все {{type}}\"\n  },\n  \"toolMultiFileResult\": {\n    \"copied\": \"Файл скопирован\",\n    \"copyFailed\": \"Не удалось скопировать: {{error}}\",\n    \"loading\": \"Загрузка... Это может занять некоторое время.\",\n    \"result\": \"Результат\"\n  },\n  \"toolMultipleAudioInput\": {\n    \"inputTitle\": \"Вход {{type}}\",\n    \"noFilesSelected\": \"Файлы не выбраны.\"\n  },\n  \"toolMultiplePdfInput\": {\n    \"inputTitle\": \"Вход {{type}}\",\n    \"noFilesSelected\": \"Файлы не выбраны.\"\n  },\n  \"toolOptions\": {\n    \"title\": \"Параметры инструмента\"\n  },\n  \"toolTextInput\": {\n    \"copied\": \"Текст скопирован\",\n    \"copyFailed\": \"Не удалось скопировать: {{error}}\",\n    \"input\": \"Введите текст\",\n    \"placeholder\": \"Введите текст здесь...\"\n  },\n  \"toolTextResult\": {\n    \"copied\": \"Текст скопирован\",\n    \"copyFailed\": \"Не удалось скопировать: {{error}}\",\n    \"loading\": \"Загрузка... Это может занять некоторое время.\",\n    \"result\": \"Результат\"\n  },\n  \"userTypes\": {\n    \"developers\": \"Разработчики\",\n    \"generalUsers\": \"Обычные пользователи\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/video.json",
    "content": "{\n  \"changeSpeed\": {\n    \"defaultMultiplier\": \"Множитель по умолчанию: 2 означает в 2 раза быстрее\",\n    \"description\": \"Изменяйте скорость воспроизведения видеофайлов. Ускоряйте или замедляйте видео, сохраняя синхронизацию со звуком. Поддерживает различные коэффициенты ускорения и распространённые видеоформаты.\",\n    \"inputTitle\": \"Входное видео\",\n    \"newVideoSpeed\": \"Новая скорость видео\",\n    \"resultTitle\": \"Отредактированное видео\",\n    \"settingSpeed\": \"Скорость установки\",\n    \"shortDescription\": \"Изменить скорость воспроизведения видео\",\n    \"title\": \"Изменить скорость видео\",\n    \"toolInfo\": {\n      \"title\": \"Что такое {{title}}?\"\n    }\n  },\n  \"compress\": {\n    \"default\": \"По умолчанию\",\n    \"description\": \"Сжимайте видео, масштабируя их до различных разрешений, таких как 240p, 480p, 720p и т. д. Этот инструмент помогает уменьшить размер файла, сохраняя приемлемое качество. Поддерживает распространённые видеоформаты, такие как MP4, WebM и OGG.\",\n    \"inputTitle\": \"Входное видео\",\n    \"loadingText\": \"Сжатие видео...\",\n    \"lossless\": \"Без потерь\",\n    \"quality\": \"Качество (CRF)\",\n    \"resolution\": \"Разрешение\",\n    \"resultTitle\": \"Сжатое видео\",\n    \"shortDescription\": \"Сжимайте видео путем масштабирования до разных разрешений\",\n    \"title\": \"Сжать видео\",\n    \"worst\": \"Худший\"\n  },\n  \"cropVideo\": {\n    \"cropCoordinates\": \"Координаты обрезки\",\n    \"croppingVideo\": \"Обрезка видео\",\n    \"description\": \"Обрежьте видео, чтобы удалить нежелательные области.\",\n    \"errorBeyondHeight\": \"Область кадрирования выходит за пределы высоты видео ({{height}}пикс)\",\n    \"errorBeyondWidth\": \"Область кадрирования выходит за пределы ширины видео ({{width}}пикс)\",\n    \"errorCroppingVideo\": \"Ошибка обрезки видео. Проверьте параметры и видеофайл.\",\n    \"errorLoadingDimensions\": \"Не удалось загрузить размеры видео.\",\n    \"errorNonNegativeCoordinates\": \"Координаты X и Y должны быть неотрицательными\",\n    \"errorPositiveDimensions\": \"Ширина и высота должны быть положительными.\",\n    \"height\": \"Высота\",\n    \"inputTitle\": \"Входное видео\",\n    \"loadVideoForDimensions\": \"Загрузите видео, чтобы увидеть размеры\",\n    \"longDescription\": \"Этот инструмент позволяет обрезать видеофайлы, удаляя нежелательные области или фокусируясь на определённых фрагментах. Он полезен для удаления чёрных полос, настройки соотношения сторон или выделения важного контента. Поддерживает различные видеоформаты, включая MP4, MOV и AVI.\",\n    \"resultTitle\": \"Обрезанное видео\",\n    \"shortDescription\": \"Обрежьте видео, чтобы удалить нежелательные области\",\n    \"title\": \"Обрезать видео\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет обрезать видеофайлы, удаляя нежелательные области. Вы можете указать область обрезки, задав координаты X, Y, а также ширину и высоту.\",\n      \"title\": \"Обрезать видео\"\n    },\n    \"videoDimensions\": \"Размеры видео: {{width}} × {{height}} пиксели\",\n    \"videoInformation\": \"Видео информация\",\n    \"width\": \"Ширина\",\n    \"xCoordinate\": \"X (слева)\",\n    \"yCoordinate\": \"Y (вверху)\"\n  },\n  \"flip\": {\n    \"description\": \"Переворачивайте видеофайлы по горизонтали или вертикали. Зеркально отображайте видео для создания спецэффектов или исправления проблем с ориентацией.\",\n    \"flippingVideo\": \"Перелистывание видео\",\n    \"horizontalLabel\": \"Горизонтальный (Зеркальный)\",\n    \"inputTitle\": \"Входное видео\",\n    \"orientation\": \"Ориентация\",\n    \"resultTitle\": \"Перевернутое видео\",\n    \"shortDescription\": \"Перевернуть видео по горизонтали или вертикали\",\n    \"title\": \"Перевернуть видео\",\n    \"verticalLabel\": \"Вертикально (вверх дном)\"\n  },\n  \"gif\": {\n    \"changeSpeed\": {\n      \"description\": \"Измените скорость воспроизведения GIF-анимации. Ускоряйте или замедляйте GIF-анимацию, сохраняя плавность анимации.\",\n      \"shortDescription\": \"Изменить скорость GIF-анимации\",\n      \"title\": \"Изменить скорость GIF-анимации\"\n    }\n  },\n  \"loop\": {\n    \"description\": \"Создайте зацикленное видео, повторив исходное видео несколько раз.\",\n    \"inputTitle\": \"Входное видео\",\n    \"loopingVideo\": \"Зацикливание видео\",\n    \"loops\": \"Петли\",\n    \"numberOfLoops\": \"Количество петель\",\n    \"resultTitle\": \"Зацикленное видео\",\n    \"shortDescription\": \"Создание зацикленных видеофайлов\",\n    \"title\": \"Цикл видео\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет создать цикличное видео, повторяя исходное видео несколько раз. Вы можете указать, сколько раз видео должно повторяться.\",\n      \"title\": \"Что такое {{title}}?\"\n    }\n  },\n  \"mergeVideo\": {\n    \"description\": \"Объедините несколько видеофайлов в одно непрерывное видео.\",\n    \"longDescription\": \"Этот инструмент позволяет объединять или добавлять несколько видеофайлов в одно непрерывное видео. Просто загрузите видеофайлы, расположите их в нужном порядке и объедините в один файл для удобного обмена или редактирования.\",\n    \"shortDescription\": \"Легко добавляйте и объединяйте видео.\",\n    \"title\": \"Объединить видео\"\n  },\n  \"rotate\": {\n    \"180Degrees\": \"180° (вверх дном)\",\n    \"270Degrees\": \"270° (90° против часовой стрелки)\",\n    \"90Degrees\": \"90° по часовой стрелке\",\n    \"description\": \"Поворачивайте видеофайлы на 90, 180 или 270 градусов. Корректируйте ориентацию видео или создавайте спецэффекты с помощью точного управления поворотом.\",\n    \"inputTitle\": \"Входное видео\",\n    \"resultTitle\": \"Повернутое видео\",\n    \"rotatingVideo\": \"Вращение видео\",\n    \"rotation\": \"Вращение\",\n    \"shortDescription\": \"Повернуть видео на указанный угол\",\n    \"title\": \"Повернуть видео\"\n  },\n  \"trim\": {\n    \"description\": \"Обрезайте видеофайлы, указав время начала и окончания. Удаляйте ненужные фрагменты в начале или конце видео.\",\n    \"endTime\": \"Конец Времени\",\n    \"inputTitle\": \"Входное видео\",\n    \"resultTitle\": \"Обрезанное видео\",\n    \"shortDescription\": \"Обрежьте видео, удалив ненужные фрагменты\",\n    \"startTime\": \"Время начала\",\n    \"timestamps\": \"Временные метки\",\n    \"title\": \"Обрезать видео\"\n  },\n  \"videoToGif\": {\n    \"description\": \"Конвертируйте видеофайлы в анимированный GIF-формат. Извлекайте определённые временные диапазоны и создавайте анимированные изображения для публикации.\",\n    \"shortDescription\": \"Конвертировать видео в анимированный GIF\",\n    \"title\": \"Видео в GIF\"\n  }\n}\n"
  },
  {
    "path": "public/locales/ru/xml.json",
    "content": "{\n  \"xmlBeautifier\": {\n    \"description\": \"Отформатируйте XML с правильными отступами и интервалами.\",\n    \"indentation\": \"Отступ\",\n    \"inputTitle\": \"Входной XML\",\n    \"resultTitle\": \"Украшенный XML\",\n    \"shortDescription\": \"Форматируйте и украшайте XML-код\",\n    \"title\": \"XML Beautifier\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет форматировать XML-данные с правильными отступами и интервалами, делая их более читабельными и удобными для работы.\",\n      \"title\": \"XML Beautifier\"\n    },\n    \"useSpaces\": \"Используйте пробелы\",\n    \"useSpacesDescription\": \"Отступ вывода с пробелами\",\n    \"useTabs\": \"Использовать вкладки\",\n    \"useTabsDescription\": \"Отступ вывода с помощью табуляции.\"\n  },\n  \"xmlValidator\": {\n    \"description\": \"Проверьте синтаксис и структуру XML.\",\n    \"placeholder\": \"Вставьте или импортируйте XML сюда...\",\n    \"shortDescription\": \"Проверить XML-код на наличие ошибок\",\n    \"title\": \"XML-валидатор\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет проверить синтаксис и структуру XML. Он проверяет правильность формата XML и выводит подробные сообщения об ошибках при обнаружении любых проблем.\",\n      \"title\": \"XML-валидатор\"\n    }\n  },\n  \"xmlViewer\": {\n    \"description\": \"Просмотр и исследование структуры XML в древовидной форме.\",\n    \"inputTitle\": \"Входной XML\",\n    \"resultTitle\": \"XML-дерево\",\n    \"title\": \"XML-просмотрщик\",\n    \"toolInfo\": {\n      \"description\": \"Этот инструмент позволяет просматривать XML-данные в формате иерархического дерева, что упрощает изучение и понимание структуры XML-документов.\",\n      \"title\": \"XML-просмотрщик\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/audio.json",
    "content": "{\n  \"changeSpeed\": {\n    \"description\": \"更改音频文件的播放速度。在保持音调不变的情况下加快或减慢音频速度。\",\n    \"inputTitle\": \"输入音频\",\n    \"newAudioSpeed\": \"新音频速度\",\n    \"outputFormat\": \"输出格式\",\n    \"resultTitle\": \"编辑音频\",\n    \"settingSpeed\": \"设定速度\",\n    \"shortDescription\": \"更改音频文件的速度\",\n    \"speedDescription\": \"默认乘数：2 表示速度快 2 倍\",\n    \"title\": \"更改音频速度\",\n    \"toolInfo\": {\n      \"title\": \"什么是 {{title}}？\"\n    }\n  },\n  \"extractAudio\": {\n    \"description\": \"从视频文件中提取音轨。\",\n    \"extractingAudio\": \"提取音频\",\n    \"inputTitle\": \"输入视频\",\n    \"outputFormat\": \"输出格式\",\n    \"outputFormatDescription\": \"选择要提取的音频的格式。\",\n    \"resultTitle\": \"提取音频\",\n    \"shortDescription\": \"将视频文件（MP4、MOV 等）中的音频提取为 AAC、MP3 或 WAV。\",\n    \"title\": \"从视频中提取音频\",\n    \"toolInfo\": {\n      \"description\": \"此工具可让您从视频文件中提取音轨。您可以选择不同的音频格式，包括 AAC、MP3 和 WAV。\",\n      \"title\": \"什么是 {{title}}？\"\n    }\n  },\n  \"mergeAudio\": {\n    \"description\": \"按顺序连接多个音频文件，将其合并为一个音频文件。\",\n    \"inputTitle\": \"输入音频文件\",\n    \"longDescription\": \"此工具允许您将多个音频文件按照上传顺序串联成一个文件。非常适合合并播客片段、音乐曲目或任何需要合并的音频文件。支持多种音频格式，包括 MP3、AAC 和 WAV。\",\n    \"mergingAudio\": \"合并音频\",\n    \"outputFormat\": \"输出格式\",\n    \"resultTitle\": \"合并音频\",\n    \"shortDescription\": \"将多个音频文件合并为一个（MP3、AAC、WAV）。\",\n    \"title\": \"合并音频\",\n    \"toolInfo\": {\n      \"title\": \"什么是 {{title}}？\"\n    }\n  },\n  \"trim\": {\n    \"description\": \"通过指定开始和结束时间来剪切和修剪音频文件以提取特定片段。\",\n    \"endTime\": \"结束时间\",\n    \"endTimeDescription\": \"结束时间格式为 HH:MM:SS（例如 00:01:30）\",\n    \"inputTitle\": \"输入音频\",\n    \"longDescription\": \"此工具允许您通过指定开始和结束时间来修剪音频文件。您可以从较长的音频文件中提取特定片段，删除不需要的部分，或创建较短的片段。支持多种音频格式，包括 MP3、AAC 和 WAV。非常适合播客编辑、音乐制作或任何音频编辑需求。\",\n    \"outputFormat\": \"输出格式\",\n    \"resultTitle\": \"修剪音频\",\n    \"shortDescription\": \"修剪音频文件以提取特定时间段（MP3、AAC、WAV）。\",\n    \"startTime\": \"开始时间\",\n    \"startTimeDescription\": \"开始时间格式为 HH:MM:SS（例如 00:00:30）\",\n    \"timeSettings\": \"时间设置\",\n    \"title\": \"修剪音频\",\n    \"toolInfo\": {\n      \"title\": \"什么是 {{title}}？\"\n    },\n    \"trimmingAudio\": \"修剪音频\"\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/converters.json",
    "content": "{\n  \"audioConverter\": {\n    \"title\": \"音频转换器\",\n    \"description\": \"在不同格式之间转换音频文件。\",\n    \"shortDescription\": \"将音频文件转换为各种格式。\",\n    \"longDescription\": \"此工具允许您将音频文件从一种格式转换为另一种格式，支持多种音频格式的无缝转换。\",\n    \"outputFormat\": \"输出格式\",\n    \"outputFormatDescription\": \"选择所需的输出音频格式\",\n    \"inputTitle\": \"音频输入\",\n    \"outputTitle\": \"转换后的音频\"\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/csv.json",
    "content": "{\n  \"changeCsvSeparator\": {\n    \"description\": \"更改 CSV 文件中的分隔符。在不同的 CSV 格式（例如逗号、分号、制表符或自定义分隔符）之间进行转换。\",\n    \"shortDescription\": \"更改 CSV 文件分隔符\",\n    \"title\": \"更改 CSV 分隔符\"\n  },\n  \"csvRowsToColumns\": {\n    \"description\": \"此工具可将 CSV（逗号分隔值）文件的行转换为列。它会从输入的 CSV 文件中逐行提取水平线，将其旋转 90 度，然后将其输出为垂直列，并以逗号分隔。', longDescription: '此工具可将 CSV（逗号分隔值）文件的行转换为列。例如，如果输入的 CSV 数据有 6 行，则输出将有 6 列，并且行中的元素将按从上到下的顺序排列。在格式正确的 CSV 文件中，每行的值数量相同。但是，如果行中缺少字段，程序可以修复它们，您可以从可用选项中进行选择：用空元素填充缺失数据，或用自定义元素（例如“missing”、“?”或“x”）替换缺失数据。在转换过程中，该工具还会清除 CSV 文件中的不必要信息，例如空行（这些行没有可见的信息）和注释。为了帮助工具正确识别注释，您可以在选项中指定行首的注释符号。此符号通常是井号“#”或双斜杠“//”。CSV 真棒！\",\n    \"longDescription\": \"此工具可将 CSV（逗号分隔值）文件的行转换为列。例如，如果输入的 CSV 数据有 6 行，则输出将有 6 列，并且行中的元素将按从上到下的顺序排列。在格式正确的 CSV 文件中，每行的值数量相同。但是，如果行中缺少字段，程序可以修复它们，您可以从以下选项中进行选择：用空元素填充缺失数据，或用自定义元素替换缺失数据，例如\",\n    \"shortDescription\": \"将 CSV 行转换为列。\",\n    \"title\": \"将 CSV 行转换为列\"\n  },\n  \"csvToJson\": {\n    \"columnSeparator\": \"列分隔符（例如，，；\\\\t）\",\n    \"commentSymbol\": \"注释符号（例如 #）\",\n    \"conversionOptions\": \"转换选项\",\n    \"description\": \"将 CSV 文件转换为 JSON 格式，并可自定义分隔符、引号和输出格式。支持标题、注释和动态类型转换。\",\n    \"dynamicTypes\": \"动态类型\",\n    \"dynamicTypesDescription\": \"自动转换数字和布尔值\",\n    \"error\": \"错误\",\n    \"errorParsing\": \"解析 CSV 时出错： {{error}}\",\n    \"fieldQuote\": \"字段引号（例如“）\",\n    \"inputCsvFormat\": \"输入 CSV 格式\",\n    \"inputTitle\": \"输入 CSV\",\n    \"invalidCsvFormat\": \"CSV格式无效\",\n    \"resultTitle\": \"输出 JSON\",\n    \"shortDescription\": \"将 CSV 数据转换为 JSON 格式。\",\n    \"skipEmptyLines\": \"跳过空行\",\n    \"skipEmptyLinesDescription\": \"忽略输入 CSV 中的空行\",\n    \"title\": \"将 CSV 转换为 JSON\",\n    \"toolInfo\": {\n      \"description\": \"此工具可将逗号分隔值 (CSV) 文件转换为 JavaScript 对象表示法 (JSON) 数据结构。它支持各种 CSV 格式，并带有可自定义的分隔符、引号和注释符号。转换器可以将第一行视为标题，跳过空行，并自动检测数字和布尔值等数据类型。生成的 JSON 可用于数据迁移、备份或作为其他应用程序的输入。\",\n      \"title\": \"什么是 CSV 到 JSON 转换器？\"\n    },\n    \"useHeaders\": \"使用标题\",\n    \"useHeadersDescription\": \"将第一行视为列标题\"\n  },\n  \"csvToTsv\": {\n    \"description\": \"使用下方表单上传您的 CSV 文件，它将自动转换为 TSV 文件。在工具选项中，您可以自定义输入的 CSV 格式——指定字段分隔符、引号和注释符号，以及跳过空的 CSV 行，并选择是否保留 CSV 列标题。\",\n    \"longDescription\": \"此工具将逗号分隔值 (CSV) 数据转换为制表符分隔值 (TSV)。CSV 和 TSV 都是用于存储表格数据的常用文件格式，但它们使用不同的分隔符来分隔值——CSV 使用逗号 (\",\n    \"shortDescription\": \"将 CSV 数据转换为 TSV 格式。\",\n    \"title\": \"将 CSV 转换为 TSV\"\n  },\n  \"csvToXml\": {\n    \"description\": \"使用可自定义的选项将 CSV 文件转换为 XML 格式。\",\n    \"shortDescription\": \"将 CSV 数据转换为 XML 格式。\",\n    \"title\": \"将 CSV 转换为 XML\"\n  },\n  \"csvToYaml\": {\n    \"description\": \"只需在下方表单中上传您的 CSV 文件，它就会自动转换为 YAML 文件。在工具选项中，您可以指定字段分隔符、字段引号和注释符，以使工具适应自定义 CSV 格式。此外，您还可以选择输出 YAML 格式：保留 CSV 标头或不包含 CSV 标头。\",\n    \"longDescription\": \"此工具可将 CSV（逗号分隔值）数据转换为 YAML（另一种标记语言）数据。CSV 是一种简单的表格格式，用于表示由行和列组成的矩阵式数据类型。而 YAML 是一种更高级的格式（实际上是 JSON 的超集），它能够创建更易于序列化的数据，并且支持列表、字典和嵌套对象。此程序支持多种 CSV 输入格式——输入数据可以使用逗号分隔（默认）、分号分隔、竖线分隔或使用其他完全不同的分隔符。您可以在选项中指定数据使用的确切分隔符。同样，您可以在选项中指定用于包装 CSV 字段的引号字符（默认为双引号）。您还可以通过在选项中指定注释符号来跳过以注释开头的行。这样可以通过跳过不必要的行来保持数据整洁。有两种方法可以将 CSV 转换为 YAML。第一种方法将每个 CSV 行转换为 YAML 列表。第二种方法从第一个 CSV 行中提取标题，并根据这些标题创建带有键的 YAML 对象。您还可以通过指定 YAML 结构缩进的空格数来自定义输出 YAML 格式。如果您需要执行反向转换，即将 YAML 转换为 CSV，可以使用我们的“将 YAML 转换为 CSV”工具。CSV 真棒！\",\n    \"shortDescription\": \"快速将 CSV 文件转换为 YAML 文件。\",\n    \"title\": \"将 CSV 转换为 YAML\"\n  },\n  \"findIncompleteCsvRecords\": {\n    \"checkingOptions\": \"检查选项\",\n    \"commentCharacterDescription\": \"输入表示注释行开始的字符。以此符号开头的行将被跳过。\",\n    \"csvInputOptions\": \"CSV 输入选项\",\n    \"csvSeparatorDescription\": \"输入用于分隔 CSV 输入文件中列的字符。\",\n    \"deleteLinesWithNoData\": \"删除没有数据的行\",\n    \"deleteLinesWithNoDataDescription\": \"从 CSV 输入文件中删除空行。\",\n    \"description\": \"只需将您的 CSV 文件上传到下方表单，此工具就会自动检查所有行或列是否均缺失值。在工具选项中，您可以调整输入文件格式（指定分隔符、引号和注释符）。此外，您还可以启用空值检查、跳过空行，以及设置输出中错误消息的数量限制。\",\n    \"findEmptyValues\": \"查找空值\",\n    \"findEmptyValuesDescription\": \"显示有关 CSV 字段为空的消息（这些不是缺失的字段，而是不包含任何内容的字段）。\",\n    \"inputTitle\": \"输入 CSV\",\n    \"limitNumberOfMessages\": \"限制消息数量\",\n    \"messageLimitDescription\": \"设置输出中消息数量的限制。\",\n    \"quoteCharacterDescription\": \"输入用于引用 CSV 输入字段的引号字符。\",\n    \"resultTitle\": \"CSV 状态\",\n    \"shortDescription\": \"快速查找 CSV 中缺少值的行和列。\",\n    \"title\": \"查找不完整的 CSV 记录\",\n    \"toolInfo\": {\n      \"title\": \"什么是 {{title}}？\"\n    }\n  },\n  \"insertCsvColumns\": {\n    \"appendColumns\": \"附加列\",\n    \"commentCharacterDescription\": \"输入表示注释行开始的字符。以此符号开头的行将被跳过。\",\n    \"csvOptions\": \"CSV 选项\",\n    \"csvSeparator\": \"CSV 分隔符\",\n    \"csvToInsert\": \"要插入的 CSV\",\n    \"csvToInsertDescription\": \"输入要插入到 CSV 中的一个或多个列。用于分隔列的字符必须与 CSV 输入文件中的字符相同。注：空行将被忽略\",\n    \"customFillDescription\": \"如果输入的 CSV 文件不完整（缺少值），则向记录中添加空字段或自定义符号以制作格式良好的 CSV？\",\n    \"customFillValueDescription\": \"使用此自定义值填写缺失的字段。（仅适用于上述“自定义值”模式。）\",\n    \"customPosition\": \"自定义位置\",\n    \"customPositionOptionsDescription\": \"选择在 CSV 文件中插入列的方法。\",\n    \"description\": \"在指定位置向 CSV 数据添加新列。\",\n    \"fillWithCustomValues\": \"填写海关价值\",\n    \"fillWithEmptyValues\": \"填充空值\",\n    \"headerName\": \"标头名称\",\n    \"headerNameDescription\": \"您想要在其后插入列的列标题。\",\n    \"inputTitle\": \"输入 CSV\",\n    \"insertingPositionDescription\": \"指定在 CSV 文件中插入列的位置。\",\n    \"position\": \"位置\",\n    \"positionOptions\": \"位置选项\",\n    \"prependColumns\": \"添加列\",\n    \"quoteCharDescription\": \"输入用于引用 CSV 输入字段的引号字符。\",\n    \"resultTitle\": \"输出 CSV\",\n    \"rowNumberDescription\": \"您想要在其后插入列的列号。\",\n    \"separatorDescription\": \"输入用于分隔 CSV 输入文件中列的字符。\",\n    \"shortDescription\": \"在 CSV 文件中的任何位置快速插入一个或多个新列。\",\n    \"title\": \"插入 CSV 列\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您在 CSV 数据的指定位置插入新列。您可以根据标题名称或列号在自定义位置添加、追加或插入列。\",\n      \"title\": \"插入 CSV 列\"\n    }\n  },\n  \"swapCsvColumns\": {\n    \"description\": \"只需在下方表单中上传您的 CSV 文件，指定要交换的列，该工具就会自动更改输出文件中指定列的位置。在工具选项中，您可以指定要交换的列位置或名称，还可以修复不完整的数据，并选择性地删除空记录和已注释掉的记录。\",\n    \"longDescription\": \"此工具通过交换列的位置来重新组织 CSV 数据。交换列可以将常用数据放在一起或放在最前面，以便于数据比较和编辑，从而提高 CSV 文件的可读性。例如，您可以将第一列与最后一列交换，或者将第二列与第三列交换。要根据列的位置交换列，请选择\",\n    \"shortDescription\": \"重新排序 CSV 列。\",\n    \"title\": \"交换 CSV 列\"\n  },\n  \"transposeCsv\": {\n    \"description\": \"只需使用下方表格上传您的 CSV 文件，此工具就会自动转置您的 CSV 文件。在工具选项中，您可以指定 CSV 文件中注释行的起始字符，以将其移除。此外，如果 CSV 文件不完整（缺少值），您可以使用空字符或自定义字符替换缺失值。\",\n    \"longDescription\": \"此工具用于转置逗号分隔值 (CSV)。它将 CSV 视为数据矩阵，并将所有元素沿主对角线翻转。输出包含与输入相同的 CSV 数据，但现在所有行都变成了列，所有列都变成了行。转置后，CSV 文件的尺寸将相反。例如，如果输入文件有 4 列 3 行，则输出文件将有 3 列 4 行。在转换过程中，程序还会清除数据中不必要的行，并更正不完整的数据。具体来说，该工具会自动删除所有以特定字符开头的空记录和注释，您可以在选项中设置这些字符。此外，如果 CSV 数据损坏或丢失，该实用程序会使用空字段或自定义字段（可在选项中指定）来填充文件。CSV 真是太棒了！\",\n    \"shortDescription\": \"快速转置 CSV 文件。\",\n    \"title\": \"转置 CSV\"\n  },\n  \"tsvToJson\": {\n    \"description\": \"将 TSV（制表符分隔值）数据转换为 JSON 格式。将表格数据转换为结构化的 JSON 对象。\",\n    \"shortDescription\": \"将 TSV 转换为 JSON 格式\",\n    \"title\": \"TSV 到 JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/image.json",
    "content": "{\n  \"changeColors\": {\n    \"description\": \"世界\",\n    \"shortDescription\": \"快速交换图像中的颜色\",\n    \"title\": \"改变图像的颜色\"\n  },\n  \"changeOpacity\": {\n    \"description\": \"轻松调整图片透明度。只需上传图片，使用滑块在 0（完全透明）和 1（完全不透明）之间设置所需的不透明度，然后下载修改后的图片即可。\",\n    \"shortDescription\": \"调整图像的透明度\",\n    \"title\": \"更改图像不透明度\"\n  },\n  \"compress\": {\n    \"compressedSize\": \"压缩尺寸\",\n    \"compressionOptions\": \"压缩选项\",\n    \"description\": \"在保持质量的同时减小图像文件大小。\",\n    \"failedToCompress\": \"压缩图片失败，请重试。\",\n    \"fileSizes\": \"文件大小\",\n    \"inputTitle\": \"输入图像\",\n    \"maxFileSizeDescription\": \"最大文件大小（以兆字节为单位）\",\n    \"originalSize\": \"原始尺寸\",\n    \"qualityDescription\": \"图像质量百分比（越低表示文件大小越小）\",\n    \"resultTitle\": \"压缩图像\",\n    \"shortDescription\": \"压缩图像以减小文件大小，同时保持合理的质量。\",\n    \"title\": \"压缩图像\"\n  },\n  \"compressPng\": {\n    \"description\": \"这是一个压缩 PNG 图片的程序。只要您将 PNG 图片粘贴到输入区域，程序就会对其进行压缩，并在输出区域显示结果。在选项中，您可以调整压缩级别，以及查看新旧图片文件的大小。\",\n    \"shortDescription\": \"快速压缩 PNG\",\n    \"title\": \"压缩 png\"\n  },\n  \"convertJgpToPng\": {\n    \"description\": \"快速将您的 JPG 图像转换为 PNG。只需在左侧的编辑器中导入您的 PNG 图像即可\",\n    \"shortDescription\": \"快速将您的 JPG 图像转换为 PNG\",\n    \"title\": \"JPG 转换为 PNG\"\n  },\n  \"convertToJpg\": {\n    \"description\": \"将各种图像格式（PNG、GIF、TIF、PSD、SVG、WEBP、HEIC、RAW）转换为 JPG，并可自定义质量和背景颜色设置。\",\n    \"shortDescription\": \"将图像转换为 JPG 格式并进行质量控制\",\n    \"title\": \"将图像转换为 JPG\"\n  },\n  \"createTransparent\": {\n    \"description\": \"世界\",\n    \"shortDescription\": \"快速使图像透明\",\n    \"title\": \"创建透明 PNG\"\n  },\n  \"crop\": {\n    \"description\": \"裁剪图像以删除不需要的区域。\",\n    \"inputTitle\": \"输入图像\",\n    \"resultTitle\": \"裁剪图像\",\n    \"shortDescription\": \"快速裁剪图像。\",\n    \"title\": \"裁剪图片\"\n  },\n  \"editor\": {\n    \"description\": \"高级图像编辑器，包含裁剪、旋转、注释、调整颜色和添加水印等工具。直接在浏览器中使用专业级工具编辑图像。\",\n    \"shortDescription\": \"使用高级工具和功能编辑图像\",\n    \"title\": \"图像编辑器\"\n  },\n  \"imageToText\": {\n    \"description\": \"使用光学字符识别 (OCR) 从图像 (JPG、PNG) 中提取文本。\",\n    \"shortDescription\": \"使用 OCR 从图像中提取文本。\",\n    \"title\": \"图像转文本 (OCR)\"\n  },\n  \"qrCode\": {\n    \"description\": \"为不同数据类型生成二维码：URL、文本、电子邮件、电话、短信、WiFi、vCard 等。\",\n    \"shortDescription\": \"为各种数据格式创建定制的二维码。\",\n    \"title\": \"二维码生成器\"\n  },\n  \"removeBackground\": {\n    \"description\": \"世界\",\n    \"shortDescription\": \"自动删除图像的背景\",\n    \"title\": \"从图像中删除背景\"\n  },\n  \"resize\": {\n    \"description\": \"将图像调整为不同的尺寸。\",\n    \"dimensionType\": \"尺寸类型\",\n    \"heightDescription\": \"高度（像素）\",\n    \"inputTitle\": \"输入图像\",\n    \"maintainAspectRatio\": \"保持纵横比\",\n    \"maintainAspectRatioDescription\": \"保持图像的原始纵横比。\",\n    \"percentage\": \"百分比\",\n    \"percentageDescription\": \"原始大小的百分比（例如，50 表示一半大小，200 表示两倍大小）\",\n    \"resizeByPercentage\": \"按百分比调整大小\",\n    \"resizeByPercentageDescription\": \"通过指定原始大小的百分比来调整大小。\",\n    \"resizeByPixels\": \"按像素调整大小\",\n    \"resizeByPixelsDescription\": \"通过指定像素尺寸来调整大小。\",\n    \"resizeMethod\": \"调整大小方法\",\n    \"resultTitle\": \"调整大小的图像\",\n    \"setHeight\": \"设置高度\",\n    \"setHeightDescription\": \"以像素为单位指定高度并根据纵横比计算宽度。\",\n    \"setWidth\": \"设置宽度\",\n    \"setWidthDescription\": \"以像素为单位指定宽度并根据纵横比计算高度。\",\n    \"shortDescription\": \"轻松调整图像大小。\",\n    \"title\": \"调整图像大小\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您调整 JPG、PNG、SVG 或 GIF 图像的大小。您可以指定像素或百分比来调整大小，并可选择保持原始宽高比。\",\n      \"title\": \"调整图像大小\"\n    },\n    \"widthDescription\": \"宽度（像素）\"\n  },\n  \"rotate\": {\n    \"description\": \"按指定角度旋转图像。\",\n    \"shortDescription\": \"轻松旋转图像。\",\n    \"title\": \"旋转图像\"\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/json.json",
    "content": "{\n  \"comparison\": {\n    \"description\": \"比较两个 JSON 对象以识别结构和值的差异。\",\n    \"shortDescription\": \"查找两个 JSON 对象之间的差异\",\n    \"title\": \"比较 JSON\"\n  },\n  \"escapeJson\": {\n    \"description\": \"转义 JSON 字符串中的特殊字符。将 JSON 数据转换为正确的转义格式，以便安全传输或存储。\",\n    \"shortDescription\": \"转义 JSON 中的特殊字符\",\n    \"title\": \"转义 JSON\"\n  },\n  \"jsonToXml\": {\n    \"description\": \"将 JSON 数据转换为 XML 格式。将结构化的 JSON 对象转换为格式良好的 XML 文档。\",\n    \"shortDescription\": \"将 JSON 转换为 XML 格式\",\n    \"title\": \"JSON 到 XML\"\n  },\n  \"minify\": {\n    \"description\": \"从 JSON 中删除所有不必要的空格。\",\n    \"inputTitle\": \"输入 JSON\",\n    \"resultTitle\": \"最小化 JSON\",\n    \"shortDescription\": \"通过删除空格来缩小 JSON\",\n    \"title\": \"最小化 JSON\",\n    \"toolInfo\": {\n      \"description\": \"JSON 压缩是指在保持 JSON 数据有效性的同时，从中移除所有不必要的空格字符的过程。这包括移除 JSON 正确解析所需的空格、换行符和缩进。压缩可以减小 JSON 数据的大小，使其在保持完全相同的数据结构和值的同时，更高效地存储和传输。\",\n      \"title\": \"什么是 JSON 最小化？\"\n    }\n  },\n  \"prettify\": {\n    \"description\": \"使用适当的缩进和间距来格式化 JSON。\",\n    \"indentation\": \"缩进\",\n    \"inputTitle\": \"输入 JSON\",\n    \"resultTitle\": \"美化 JSON\",\n    \"shortDescription\": \"格式化并美化 JSON 代码\",\n    \"title\": \"美化 JSON\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您使用适当的缩进和间距格式化 JSON 数据，使其更具可读性且更易于使用。\",\n      \"title\": \"美化 JSON\"\n    },\n    \"useSpaces\": \"使用空格\",\n    \"useSpacesDescription\": \"使用空格缩进输出\",\n    \"useTabs\": \"使用标签\",\n    \"useTabsDescription\": \"使用制表符缩进输出。\"\n  },\n  \"stringify\": {\n    \"description\": \"将 JavaScript 对象转换为 JSON 字符串格式。将数据结构序列化为 JSON 字符串以便存储或传输。\",\n    \"shortDescription\": \"将对象转换为 JSON 字符串\",\n    \"title\": \"字符串化 JSON\"\n  },\n  \"validateJson\": {\n    \"description\": \"检查 JSON 是否有效且格式正确。\",\n    \"inputTitle\": \"输入 JSON\",\n    \"invalidJson\": \"❌ {{error}}\",\n    \"resultTitle\": \"验证结果\",\n    \"shortDescription\": \"验证 JSON 代码是否存在错误\",\n    \"title\": \"验证 JSON\",\n    \"toolInfo\": {\n      \"description\": \"JSON（JavaScript 对象表示法）是一种轻量级数据交换格式。JSON 验证可确保数据结构符合 JSON 标准。有效的 JSON 对象必须具备以下特征：- 属性名称用双引号括起来。- 正确匹配的花括号 {}。- 最后一个键值对后没有尾随逗号。- 对象和数组的嵌套正确。此工具会检查输入的 JSON 并提供反馈，以帮助识别和修复常见错误。\",\n      \"title\": \"什么是 JSON 验证？\"\n    },\n    \"validJson\": \"✅ 有效的 JSON\"\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/list.json",
    "content": "{\n  \"duplicate\": {\n    \"concatenate\": \"连接\",\n    \"concatenateDescription\": \"连接副本（如果未选中，则项目将交织在一起）\",\n    \"copyDescription\": \"份数（可以是分数）\",\n    \"description\": \"世界上最简单的基于浏览器的列表项复制实用程序。输入您的列表并指定复制条件即可创建项目副本。非常适合数据扩展、测试或创建重复模式。\",\n    \"duplicationOptions\": \"复制选项\",\n    \"error\": \"错误\",\n    \"example1Description\": \"此示例显示如何复制单词列表。\",\n    \"example1Title\": \"简单复制\",\n    \"example2Description\": \"此示例显示如何以相反的顺序复制列表。\",\n    \"example2Title\": \"反向复制\",\n    \"example3Description\": \"此示例显示如何交织项目而不是连接它们。\",\n    \"example3Title\": \"交织物品\",\n    \"example4Description\": \"此示例显示如何复制具有小数份副本的列表。\",\n    \"example4Title\": \"部分重复\",\n    \"examples\": {\n      \"fractional\": {\n        \"description\": \"此示例显示如何复制具有小数份副本的列表。\",\n        \"title\": \"部分重复\"\n      },\n      \"interweave\": {\n        \"description\": \"此示例显示如何交织项目而不是连接它们。\",\n        \"title\": \"交织物品\"\n      },\n      \"reverse\": {\n        \"description\": \"此示例显示如何以相反的顺序复制列表。\",\n        \"title\": \"反向复制\"\n      },\n      \"simple\": {\n        \"description\": \"此示例显示如何复制单词列表。\",\n        \"title\": \"简单复制\"\n      }\n    },\n    \"inputTitle\": \"输入列表\",\n    \"joinSeparatorDescription\": \"用于连接重复列表的分隔符\",\n    \"resultTitle\": \"重复列表\",\n    \"reverse\": \"撤销\",\n    \"reverseDescription\": \"反转重复项\",\n    \"shortDescription\": \"按照指定条件复制列表项\",\n    \"splitByRegex\": \"按正则表达式拆分\",\n    \"splitBySymbol\": \"按符号拆分\",\n    \"splitOptions\": \"拆分选项\",\n    \"splitSeparatorDescription\": \"用于分割列表的分隔符\",\n    \"title\": \"复制\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您复制列表中的项目。您可以指定副本数量（包括小数），控制项目是连接还是交织，甚至可以反转重复的项目。它非常适合创建重复模式、生成测试数据或扩展具有可预测内容的列表。\",\n      \"title\": \"列表重复\"\n    },\n    \"unknownError\": \"发生未知错误\",\n    \"validation\": {\n      \"copyMustBeNumber\": \"份数必须是数字\",\n      \"copyMustBePositive\": \"副本数量必须为正数\",\n      \"copyRequired\": \"需要复印份数\",\n      \"joinSeparatorRequired\": \"连接分隔符是必需的\",\n      \"separatorRequired\": \"分隔符是必需的\"\n    }\n  },\n  \"findMostPopular\": {\n    \"description\": \"世界上最简单的基于浏览器的实用程序，用于查找列表中最热门的项目。输入您的列表，即可立即获取出现频率最高的项目。非常适合数据分析、趋势识别或查找共同点。\",\n    \"displayFormatDescription\": \"如何显示最受欢迎的列表项？\",\n    \"displayOptions\": {\n      \"count\": \"显示项目数\",\n      \"percentage\": \"显示项目百分比\",\n      \"total\": \"显示商品总数\"\n    },\n    \"extractListItems\": \"如何提取列表项？\",\n    \"ignoreItemCase\": \"忽略项目大小写\",\n    \"ignoreItemCaseDescription\": \"比较所有列表项的小写形式。\",\n    \"inputTitle\": \"输入列表\",\n    \"itemComparison\": \"商品比较\",\n    \"outputFormat\": \"顶级项目输出格式\",\n    \"removeEmptyItems\": \"删除空项目\",\n    \"removeEmptyItemsDescription\": \"从比较中忽略空项。\",\n    \"resultTitle\": \"最受欢迎的商品\",\n    \"shortDescription\": \"查找最常出现的项目\",\n    \"sortOptions\": {\n      \"alphabetic\": \"按字母顺序排序\",\n      \"count\": \"按数量排序\"\n    },\n    \"sortingMethodDescription\": \"选择排序方法。\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"使用正则表达式分隔输入列表项。\",\n        \"title\": \"使用正则表达式进行拆分\"\n      },\n      \"symbol\": {\n        \"description\": \"用字符分隔输入列表项。\",\n        \"title\": \"使用符号进行拆分\"\n      }\n    },\n    \"splitSeparatorDescription\": \"设置分隔符号或正则表达式。\",\n    \"title\": \"查找最受欢迎的\",\n    \"trimItems\": \"修剪顶部列表项\",\n    \"trimItemsDescription\": \"在比较项目之前删除前导空格和尾随空格\"\n  },\n  \"findUnique\": {\n    \"caseSensitiveItems\": \"区分大小写的项目\",\n    \"caseSensitiveItemsDescription\": \"将具有不同大小写的项目作为列表中的唯一元素输出。\",\n    \"delimiterDescription\": \"设置分隔符号或正则表达式。\",\n    \"description\": \"世界上最简单的基于浏览器的实用程序，用于在列表中查找唯一项。输入您的列表，即可立即获取所有唯一值（已删除重复项）。非常适合数据清理、重复数据删除或查找不同元素。\",\n    \"findAbsolutelyUniqueItems\": \"寻找绝对独特的物品\",\n    \"findAbsolutelyUniqueItemsDescription\": \"仅显示列表中存在于单个副本中的项目。\",\n    \"inputListDelimiter\": \"输入列表分隔符\",\n    \"inputTitle\": \"输入列表\",\n    \"outputListDelimiter\": \"输出列表分隔符\",\n    \"resultTitle\": \"独特物品\",\n    \"shortDescription\": \"在列表中查找唯一项\",\n    \"skipEmptyItems\": \"跳过空项目\",\n    \"skipEmptyItemsDescription\": \"不要在输出中包含空列表项。\",\n    \"title\": \"寻找独特的\",\n    \"trimItems\": \"修剪列表项\",\n    \"trimItemsDescription\": \"比较项目之前删除前导和尾随空格。\",\n    \"uniqueItemOptions\": \"独特物品选项\"\n  },\n  \"group\": {\n    \"deleteEmptyItems\": \"删除空项目\",\n    \"deleteEmptyItemsDescription\": \"忽略空项目并且不要将它们包含在组中。\",\n    \"description\": \"世界上最简单的基于浏览器的列表项分组工具。输入您的列表并指定分组条件，即可将项目按逻辑分组。非常适合分类数据、组织信息或创建结构化列表。支持自定义分隔符和各种分组选项。\",\n    \"emptyItemsAndPadding\": \"空项目和填充\",\n    \"groupNumberDescription\": \"组中项目的数量\",\n    \"groupSeparatorDescription\": \"组分隔符\",\n    \"groupSizeAndSeparators\": \"组大小和分隔符\",\n    \"inputItemSeparator\": \"输入项分隔符\",\n    \"inputTitle\": \"输入列表\",\n    \"itemSeparatorDescription\": \"项目分隔符\",\n    \"leftWrapDescription\": \"组的左换行符号。\",\n    \"padNonFullGroups\": \"填充非满组\",\n    \"padNonFullGroupsDescription\": \"使用自定义项目（在下面输入）填充非完整组。\",\n    \"paddingCharDescription\": \"使用此字符或项目来填充非完整组。\",\n    \"resultTitle\": \"分组项目\",\n    \"rightWrapDescription\": \"组的右换行符号。\",\n    \"shortDescription\": \"按共同属性对列表项进行分组\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"使用正则表达式分隔输入列表项。\",\n        \"title\": \"使用正则表达式进行拆分\"\n      },\n      \"symbol\": {\n        \"description\": \"用字符分隔输入列表项。\",\n        \"title\": \"使用符号进行拆分\"\n      }\n    },\n    \"splitSeparatorDescription\": \"设置分隔符号或正则表达式。\",\n    \"title\": \"团体\"\n  },\n  \"reverse\": {\n    \"description\": \"这是一个基于浏览器的超级简单的应用程序，可以反向打印所有列表项。输入项可以用任意符号分隔，并且您还可以更改反向列表项的分隔符。\",\n    \"inputTitle\": \"输入列表\",\n    \"itemSeparator\": \"项目分隔符\",\n    \"itemSeparatorDescription\": \"设置分隔符号或正则表达式。\",\n    \"outputListOptions\": \"输出列表选项\",\n    \"outputSeparatorDescription\": \"输出列表项分隔符。\",\n    \"resultTitle\": \"反转列表\",\n    \"shortDescription\": \"快速反转列表\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"使用正则表达式分隔输入列表项。\",\n        \"title\": \"使用正则表达式进行拆分\"\n      },\n      \"symbol\": {\n        \"description\": \"用字符分隔输入列表项。\",\n        \"title\": \"使用符号进行拆分\"\n      }\n    },\n    \"splitterMode\": \"分离器模式\",\n    \"title\": \"撤销\",\n    \"toolInfo\": {\n      \"description\": \"使用此实用程序，您可以反转列表中项目的顺序。该实用程序首先将输入列表拆分成单个项目，然后从最后一个项目到第一个项目进行迭代，并在迭代过程中将每个项目打印到输出中。输入列表可以包含任何可以表示为文本数据的内容，包括数字、数值、字符串、单词、句子等。输入项分隔符也可以是正则表达式。例如，正则表达式 /[;,]/ 允许您使用逗号或分号分隔的项目。输入和输出列表项分隔符可以在选项中自定义。默认情况下，输入和输出列表均以逗号分隔。Listabulous！\",\n      \"title\": \"什么是列表反转器？\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"世界上最简单的基于浏览器的列表项旋转工具。输入您的列表并指定旋转量，即可将列表项移动指定的位置。非常适合数据操作、循环移动或列表重新排序。\",\n    \"shortDescription\": \"按指定位置旋转列表项\",\n    \"title\": \"旋转\"\n  },\n  \"shuffle\": {\n    \"delimiterDescription\": \"设置分隔符号或正则表达式。\",\n    \"description\": \"世界上最简单的基于浏览器的列表项随机排序工具。输入您的列表，即可立即获得一个按随机顺序排列的列表项随机版本。非常适合创建多样性、测试随机性或混合有序数据。\",\n    \"inputListSeparator\": \"输入列表分隔符\",\n    \"inputTitle\": \"输入列表\",\n    \"joinSeparatorDescription\": \"在随机列表中使用此分隔符。\",\n    \"outputLengthDescription\": \"输出这么多的随机物品\",\n    \"resultTitle\": \"随机列表\",\n    \"shortDescription\": \"随机化列表项的顺序\",\n    \"shuffledListLength\": \"打乱列表长度\",\n    \"shuffledListSeparator\": \"随机列表分隔符\",\n    \"title\": \"随机播放\"\n  },\n  \"sort\": {\n    \"caseSensitive\": \"区分大小写排序\",\n    \"caseSensitiveDescription\": \"分别对大写和小写字母进行排序。按升序排列时，大写字母优先于小写字母。（仅适用于字母排序模式。）\",\n    \"description\": \"世界上最简单的基于浏览器的列表项排序工具。输入您的列表并指定排序条件，即可按升序或降序排列项目。非常适合数据整理、文本处理或创建有序列表。\",\n    \"inputItemSeparator\": \"输入项分隔符\",\n    \"inputTitle\": \"输入列表\",\n    \"joinSeparatorDescription\": \"使用此符号作为排序列表中项目之间的连接符。\",\n    \"orderDescription\": \"选择排序顺序。\",\n    \"orderOptions\": {\n      \"decreasing\": \"降序排列\",\n      \"increasing\": \"增加订单\"\n    },\n    \"removeDuplicates\": \"删除重复项\",\n    \"removeDuplicatesDescription\": \"删除重复的列表项。\",\n    \"resultTitle\": \"排序列表\",\n    \"shortDescription\": \"按指定顺序对列表项进行排序\",\n    \"sortMethod\": \"排序方法\",\n    \"sortMethodDescription\": \"选择排序方法。\",\n    \"sortOptions\": {\n      \"alphabetic\": \"按字母顺序排序\",\n      \"length\": \"按长度排序\",\n      \"numeric\": \"按数字排序\"\n    },\n    \"sortedItemProperties\": \"排序的项目属性\",\n    \"splitOperators\": {\n      \"regex\": {\n        \"description\": \"使用正则表达式分隔输入列表项。\",\n        \"title\": \"使用正则表达式进行拆分\"\n      },\n      \"symbol\": {\n        \"description\": \"用字符分隔输入列表项。\",\n        \"title\": \"使用符号进行拆分\"\n      }\n    },\n    \"splitSeparatorDescription\": \"设置分隔符号或正则表达式。\",\n    \"title\": \"种类\"\n  },\n  \"truncate\": {\n    \"description\": \"世界上最简单的基于浏览器的列表截断工具。输入您的列表并指定要保留的最大项目数。非常适合数据处理、列表管理或限制内容长度。\",\n    \"shortDescription\": \"将列表截断为指定数量的项目\",\n    \"title\": \"截短\"\n  },\n  \"unwrap\": {\n    \"description\": \"世界上最简单的基于浏览器的列表项展开工具。输入已展开的列表，并指定展开条件以展平有序的列表项。非常适合数据处理、文本操作或从结构化列表中提取内容。\",\n    \"shortDescription\": \"从结构化格式中解开列表项\",\n    \"title\": \"展开\"\n  },\n  \"wrap\": {\n    \"description\": \"在每个列表项之前和之后添加文本。\",\n    \"inputTitle\": \"输入列表\",\n    \"joinSeparatorDescription\": \"用于连接包装列表的分隔符\",\n    \"leftTextDescription\": \"每个项目前添加的文本\",\n    \"removeEmptyItems\": \"删除空项目\",\n    \"resultTitle\": \"包装列表\",\n    \"rightTextDescription\": \"每项后添加的文本\",\n    \"shortDescription\": \"使用指定条件包装列表项\",\n    \"splitByRegex\": \"按正则表达式拆分\",\n    \"splitBySymbol\": \"按符号拆分\",\n    \"splitOptions\": \"拆分选项\",\n    \"splitSeparatorDescription\": \"用于分割列表的分隔符\",\n    \"title\": \"裹\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您在列表中每个项目前后添加文本。您可以为左右两侧指定不同的文本，并控制列表的处理方式。它适用于为列表项添加引号、括号或其他格式，准备不同格式的数据，或创建结构化文本。\",\n      \"title\": \"列表包装\"\n    },\n    \"wrapOptions\": \"包装选项\"\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/number.json",
    "content": "{\n  \"arithmeticSequence\": {\n    \"commonDifferenceDescription\": \"术语之间的共同差异（d）\",\n    \"description\": \"生成具有可定制参数的算术序列。\",\n    \"firstTermDescription\": \"序列的第一项 (a₁)\",\n    \"numberOfTermsDescription\": \"生成的术语数（n）\",\n    \"outputFormat\": \"输出格式\",\n    \"resultTitle\": \"生成的序列\",\n    \"separatorDescription\": \"术语之间的分隔符\",\n    \"sequenceParameters\": \"序列参数\",\n    \"shortDescription\": \"生成等差序列\",\n    \"title\": \"等差序列\",\n    \"toolInfo\": {\n      \"description\": \"等差数列是指一系列连续数列中，各连续项之间的差为常数。这个常数差称为公差。给定第一项 (a₁) 和公差 (d)，可以通过将公差加到前一项来求出每一项。\",\n      \"title\": \"什么是等差序列？\"\n    }\n  },\n  \"generate\": {\n    \"arithmeticSequenceOption\": \"算术序列选项\",\n    \"description\": \"生成具有可定制参数的数字序列。\",\n    \"numberOfElementsDescription\": \"序列中的元素数量。\",\n    \"resultTitle\": \"生成的数字\",\n    \"separator\": \"分隔符\",\n    \"separatorDescription\": \"用该字符分隔算术序列中的元素。\",\n    \"shortDescription\": \"生成指定范围内的随机数\",\n    \"startSequenceDescription\": \"从该数字开始序列。\",\n    \"stepDescription\": \"将每个元素增加此量\",\n    \"title\": \"产生\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您生成具有可自定义参数的数字序列。您可以指定起始值、步长和元素数量。\",\n      \"title\": \"生成数字\"\n    }\n  },\n  \"ohmsLaw\": {\n    \"description\": \"计算电压、电流和电阻\",\n    \"longDescription\": \"此计算器应用欧姆定律 (V = I × R)，在已知其他两个电参数的情况下，确定任意三个电参数。欧姆定律是电气工程中的一个基本原理，描述了电压 (V)、电流 (I) 和电阻 (R) 之间的关系。对于电子爱好者、电气工程师和从事电路设计的学生来说，此工具必不可少，可以帮助他们快速求解电气设计中的未知值。\",\n    \"shortDescription\": \"使用欧姆定律计算电路中的电压、电流或电阻\",\n    \"title\": \"欧姆定律\"\n  },\n  \"randomNumberGenerator\": {\n    \"description\": \"使用可自定义的选项生成指定范围内的随机数。\",\n    \"error\": {\n      \"generationFailed\": \"随机数生成失败，请检查输入参数。\"\n    },\n    \"info\": {\n      \"description\": \"随机数生成器会在指定范围内生成不可预测的数字。此工具使用加密安全的随机数生成技术，确保结果真正随机。适用于模拟、游戏、统计抽样和测试场景。\",\n      \"title\": \"什么是随机数生成器？\"\n    },\n    \"longDescription\": \"生成指定范围内的随机数，可选择整数或小数，允许或禁止重复，并对结果进行排序。非常适合模拟、测试、游戏和统计分析。\",\n    \"options\": {\n      \"generation\": {\n        \"allowDecimals\": {\n          \"description\": \"生成十进制数而不是整数\",\n          \"title\": \"允许小数\"\n        },\n        \"allowDuplicates\": {\n          \"description\": \"允许同一个数字出现多次\",\n          \"title\": \"允许重复\"\n        },\n        \"countDescription\": \"生成的随机数的数量（1-10,000）\",\n        \"sortResults\": {\n          \"description\": \"按升序对生成的数字进行排序\",\n          \"title\": \"对结果进行排序\"\n        },\n        \"title\": \"生成选项\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"用于分隔生成的数字的字符\",\n        \"title\": \"输出设置\"\n      },\n      \"range\": {\n        \"maxDescription\": \"最大值（含）\",\n        \"minDescription\": \"最小值（含）\",\n        \"title\": \"范围设置\"\n      }\n    },\n    \"result\": {\n      \"count\": \"数数\",\n      \"hasDuplicates\": \"包含重复项\",\n      \"isSorted\": \"已排序\",\n      \"range\": \"范围\",\n      \"title\": \"生成的随机数\"\n    },\n    \"shortDescription\": \"生成自定义范围内的随机数\",\n    \"title\": \"随机数生成器\"\n  },\n  \"randomPortGenerator\": {\n    \"description\": \"使用可定制的选项在指定范围内生成随机网络端口。\",\n    \"error\": {\n      \"generationFailed\": \"无法生成随机端口。请检查您的输入参数。\"\n    },\n    \"info\": {\n      \"description\": \"随机端口生成器可在指定范围内创建不可预测的网络端口号。此工具遵循 IANA 端口号标准，并包含常用服务的识别功能。适用于开发、测试、网络配置以及避免端口冲突。\",\n      \"title\": \"什么是随机端口生成器？\"\n    },\n    \"longDescription\": \"在指定范围内（知名、注册、动态或自定义）生成随机网络端口。非常适合开发、测试和网络配置。包含常用端口的端口服务识别。\",\n    \"options\": {\n      \"generation\": {\n        \"allowDuplicates\": {\n          \"description\": \"允许同一端口出现多次\",\n          \"title\": \"允许重复\"\n        },\n        \"countDescription\": \"要生成的随机端口数量（1-1,000）\",\n        \"sortResults\": {\n          \"description\": \"按升序对生成的端口进行排序\",\n          \"title\": \"对结果进行排序\"\n        },\n        \"title\": \"生成选项\"\n      },\n      \"output\": {\n        \"separatorDescription\": \"用于分隔生成的端口的字符\",\n        \"title\": \"输出设置\"\n      },\n      \"range\": {\n        \"custom\": \"自定义范围\",\n        \"dynamic\": \"动态端口（49152-65535）\",\n        \"maxPortDescription\": \"最大端口号（1-65535）\",\n        \"minPortDescription\": \"最小端口号（1-65535）\",\n        \"registered\": \"注册端口（1024-49151）\",\n        \"title\": \"端口范围设置\",\n        \"wellKnown\": \"知名端口（1-1023）\"\n      }\n    },\n    \"result\": {\n      \"count\": \"数数\",\n      \"hasDuplicates\": \"包含重复项\",\n      \"isSorted\": \"已排序\",\n      \"portDetails\": \"端口详细信息\",\n      \"range\": \"端口范围\",\n      \"title\": \"生成的随机端口\"\n    },\n    \"shortDescription\": \"生成随机网络端口\",\n    \"title\": \"随机端口生成器\"\n  },\n  \"slackline\": {\n    \"description\": \"计算松弛线的张力\",\n    \"longDescription\": \"该计算器假设绳索中心有负载\",\n    \"shortDescription\": \"计算松弛绳或晾衣绳的大致张力。不要依赖它来确保安全。\",\n    \"title\": \"松弛线张力\"\n  },\n  \"sphereArea\": {\n    \"description\": \"球体的面积\",\n    \"longDescription\": \"此计算器使用公式 A = 4πr² 计算球体的表面积。您可以输入半径来计算表面积，也可以直接输入表面积来计算所需的半径。此工具适用于学习几何的学生、处理球形物体的工程师以及任何需要进行球面计算的人。\",\n    \"shortDescription\": \"根据球体的半径计算其表面积\",\n    \"title\": \"球体的面积\"\n  },\n  \"sphereVolume\": {\n    \"description\": \"球体的体积\",\n    \"longDescription\": \"此计算器使用公式 V = (4/3)πr³ 计算球体的体积。您可以输入半径或直径来计算体积，也可以直接输入体积来确定所需的半径。对于在物理、工程和制造等领域处理球形物体的学生、工程师和专业人士来说，此工具非常有用。\",\n    \"shortDescription\": \"使用半径或直径计算球体的体积\",\n    \"title\": \"球体的体积\"\n  },\n  \"sum\": {\n    \"description\": \"计算一串数字的总和。输入以逗号或换行符分隔的数字，即可计算出它们的总和。\",\n    \"example1Description\": \"在此示例中，我们计算十个正整数的和。这些整数列成一列，它们的总和等于 19494。\",\n    \"example1Title\": \"十个正数之和\",\n    \"example2Description\": \"此示例反转一列二十个三音节名词，并从下到上打印所有单词。为了分隔列表项，它使用 \\\\n 字符作为输入项分隔符，这意味着每个项都独占一行。\",\n    \"example2Title\": \"数一数公园里的树木\",\n    \"example3Description\": \"在这个例子中，我们将 90 个不同的值相加——正数、负数、整数和小数。我们将输入分隔符设置为逗号，将它们全部相加后，输出结果为 0。\",\n    \"example3Title\": \"整数和小数之和\",\n    \"example4Description\": \"在此示例中，我们计算所有十位数字的总和，并启用“打印运行总和”选项。我们获取加法过程中和的中间值。因此，输出结果为以下序列：0, 1 (0 + 1), 3 (0 + 1 + 2), 6 (0 + 1 + 2 + 3), 10 (0 + 1 + 2 + 3 + 4)，依此类推。\",\n    \"example4Title\": \"数字的累计和\",\n    \"extractionTypes\": {\n      \"delimiter\": {\n        \"description\": \"在此自定义数字分隔符。（默认为换行符。）\",\n        \"title\": \"数字分隔符\"\n      },\n      \"smart\": {\n        \"description\": \"自动检测输入中的数字。\",\n        \"title\": \"智能总和\"\n      }\n    },\n    \"inputTitle\": \"输入\",\n    \"numberExtraction\": \"数字提取\",\n    \"printRunningSum\": \"打印运行总和\",\n    \"printRunningSumDescription\": \"显示逐步计算的总和。\",\n    \"resultTitle\": \"全部的\",\n    \"runningSum\": \"运行总和\",\n    \"shortDescription\": \"计算数字之和\",\n    \"title\": \"和\",\n    \"toolInfo\": {\n      \"description\": \"这是一个基于浏览器的在线实用程序，用于计算一组数字的总和。您可以输入用逗号、空格或任何其他字符（包括换行符）分隔的数字。您也可以直接粘贴一段包含要求和的数值的文本数据，该实用程序会提取这些数据并计算总和。\",\n      \"title\": \"什么是数字总和计算器？\"\n    }\n  },\n  \"voltageDropInWire\": {\n    \"description\": \"计算双芯电缆的往返电压和功率损耗\",\n    \"longDescription\": \"这款计算器可帮助计算双芯电缆的电压降和功率损耗。它考虑了电缆长度、线规（横截面积）、材料电阻率和电流。该工具可计算往返电压降、电缆总电阻以及以热量形式耗散的功率。这对于电气工程师、电工和业余爱好者在设计电气系统时尤其有用，可确保负载下的电压水平保持在可接受的范围内。\",\n    \"shortDescription\": \"根据长度、材料和电流计算电缆的电压降和功率损耗\",\n    \"title\": \"电缆往返电压降\"\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/pdf.json",
    "content": "{\n  \"compressPdf\": {\n    \"compressedFileSize\": \"压缩文件大小\",\n    \"compressingPdf\": \"正在压缩 PDF...\",\n    \"compressionLevel\": \"压缩级别\",\n    \"compressionSettings\": \"压缩设置\",\n    \"description\": \"使用 Ghostscript 减小 PDF 文件大小同时保持质量\",\n    \"errorCompressingPdf\": \"压缩 PDF 失败： {{error}}\",\n    \"errorReadingPdf\": \"无法读取 PDF 文件。请确保它是有效的 PDF。\",\n    \"fileSize\": \"原始文件大小\",\n    \"highCompression\": \"高压缩\",\n    \"highCompressionDescription\": \"最大程度地减少文件大小，但会损失一些质量\",\n    \"inputTitle\": \"输入 PDF\",\n    \"longDescription\": \"使用 Ghostscript 在浏览器中安全地压缩 PDF 文件。您的文件永远不会离开您的设备，在确保绝对隐私的同时，还能缩减文件大小，方便您通过电子邮件共享、上传到网站或节省存储空间。由 WebAssembly 技术提供支持。\",\n    \"lowCompression\": \"低压缩\",\n    \"lowCompressionDescription\": \"在尽量减少质量损失的情况下稍微减小文件大小\",\n    \"mediumCompression\": \"中等压缩\",\n    \"mediumCompressionDescription\": \"文件大小和质量之间的平衡\",\n    \"pages\": \"页数\",\n    \"resultTitle\": \"压缩 PDF\",\n    \"shortDescription\": \"在浏览器中安全地压缩 PDF 文件\",\n    \"title\": \"压缩PDF\"\n  },\n  \"editor\": {\n    \"description\": \"高级 PDF 编辑器，具备注释、表单填写、高亮显示和导出功能。您可以使用专业级工具（包括文本插入、绘图、高亮显示、签名和表单填写）直接在浏览器中编辑 PDF。\",\n    \"shortDescription\": \"使用高级注释、签名和编辑工具编辑 PDF\",\n    \"title\": \"PDF编辑器\"\n  },\n  \"merge\": {\n    \"inputTitle\": \"输入 PDF\",\n    \"loadingText\": \"提取页面\",\n    \"resultTitle\": \"输出合并的 PDF\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您将多个 PDF 文件合并为一个文档。要使用该工具，只需上传要合并的 PDF 文件即可。然后，该工具会将输入文件中的所有页面合并为一个 PDF 文档。\",\n      \"title\": \"如何使用合并 PDF 工具？\"\n    }\n  },\n  \"mergePdf\": {\n    \"description\": \"合并多个 PDF 文件为一个文档。\",\n    \"inputTitle\": \"输入 PDF\",\n    \"mergingPdfs\": \"合并 PDF\",\n    \"pdfOptions\": \"PDF 选项\",\n    \"resultTitle\": \"合并的 PDF\",\n    \"shortDescription\": \"将多个 PDF 文件合并为一个文档\",\n    \"sortByFileName\": \"按文件名排序\",\n    \"sortByFileNameDescription\": \"按文件名字母顺序对 PDF 进行排序\",\n    \"sortByUploadOrder\": \"按上传顺序排序\",\n    \"sortByUploadOrderDescription\": \"保持 PDF 按照上传顺序排列\",\n    \"title\": \"合并 PDF\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您将多个 PDF 文件合并为一个文档。您可以选择对 PDF 进行排序，该工具将按照指定的顺序进行合并。\",\n      \"title\": \"合并PDF文件\"\n    }\n  },\n  \"pdfToEpub\": {\n    \"description\": \"将 PDF 文档转换为 EPUB 文件，以实现更好的电子阅读器兼容性。\",\n    \"shortDescription\": \"将 PDF 文件转换为 EPUB 格式\",\n    \"title\": \"PDF 转 EPUB\"\n  },\n  \"pdfToPng\": {\n    \"description\": \"将 PDF 文档转换为 PNG 面板。\",\n    \"longDescription\": \"上传 PDF 后，直接在浏览器中将每个页面转换为高质量的 PNG 图像。此工具非常适合提取视觉内容或分享单个页面。无需上传数据，所有操作均在本地运行。\",\n    \"shortDescription\": \"将 PDF 转换为 PNG 图像\",\n    \"title\": \"PDF 转 PNG\"\n  },\n  \"convertToPdf\": {\n    \"title\": \"将图像转换为 PDF\",\n    \"description\": \"将各种图像格式（PNG, GIF, JPG, TIF, PSD, SVG, WEBP, HEIC, RAW）转换为 PDF，并可调整图像大小和页面方向。\",\n    \"shortDescription\": \"将图像转换为 PDF，可调整大小和方向\"\n  },\n  \"protectPdf\": {\n    \"description\": \"在浏览器中安全地为 PDF 文件添加密码保护\",\n    \"shortDescription\": \"使用密码安全地保护 PDF 文件\",\n    \"title\": \"保护 PDF\"\n  },\n  \"rotatePdf\": {\n    \"allPagesWillBeRotated\": \"全部 {{count}} 页面将会旋转\",\n    \"angleOptions\": {\n      \"clockwise90\": \"顺时针 90°\",\n      \"counterClockwise270\": \"270°（逆时针90°）\",\n      \"upsideDown180\": \"180°（上下颠倒）\"\n    },\n    \"applyToAllPages\": \"应用于所有页面\",\n    \"description\": \"旋转 PDF 文档中的页面。\",\n    \"inputTitle\": \"输入 PDF\",\n    \"longDescription\": \"通过将 PDF 页面旋转 90 度、180 度或 270 度来更改其方向。这对于修复扫描错误的文档或准备打印 PDF 非常有用。\",\n    \"pageRangesDescription\": \"输入页码或范围，以逗号分隔（例如，1,3,5-7）\",\n    \"pageRangesPlaceholder\": \"例如，1.5-8\",\n    \"pagesWillBeRotated\": \"{{count}} 页{{count !== 1 ? 's' : ''}} 将会轮换\",\n    \"pdfPageCount\": \"PDF 有 {{count}} 页{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"旋转的 PDF\",\n    \"rotatingPages\": \"旋转页面\",\n    \"rotationAngle\": \"旋转角度\",\n    \"rotationSettings\": \"旋转设置\",\n    \"shortDescription\": \"旋转 PDF 文档中的页面\",\n    \"title\": \"旋转 PDF\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您旋转 PDF 文档中的页面。您可以旋转所有页面，也可以指定单个页面进行旋转。选择旋转角度：顺时针 90°、上下 180° 或逆时针 270°。要旋转特定页面，请取消勾选“应用于所有页面”，并输入页码或以逗号分隔的范围（例如，1,3,5-7）。\",\n      \"title\": \"如何使用旋转 PDF 工具\"\n    }\n  },\n  \"splitPdf\": {\n    \"description\": \"从 PDF 文档中提取特定页面。\",\n    \"extractingPages\": \"提取页面\",\n    \"inputTitle\": \"输入 PDF\",\n    \"pageExtractionPreview\": \"{{count}} 页{{count !== 1 ? 's' : ''}} 将被提取\",\n    \"pageRangesDescription\": \"输入页码或范围，以逗号分隔（例如，1,3,5-7）\",\n    \"pageRangesPlaceholder\": \"例如，1.5-8\",\n    \"pageSelection\": \"页面选择\",\n    \"pdfPageCount\": \"PDF 有 {{count}} 页{{count !== 1 ? 's' : ''}}\",\n    \"resultTitle\": \"提取的 PDF\",\n    \"shortDescription\": \"从 PDF 文件中提取特定页面\",\n    \"title\": \"拆分 PDF\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您从 PDF 文档中提取特定页面。您可以指定要提取的单个页面或页面范围。\",\n      \"title\": \"拆分 PDF\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/string.json",
    "content": "{\n  \"base64\": {\n    \"decode\": \"Base64解码\",\n    \"description\": \"使用 Base64 编码对文本进行编码或解码。\",\n    \"encode\": \"Base64编码\",\n    \"inputTitle\": \"输入数据\",\n    \"optionsTitle\": \"Base64 选项\",\n    \"resultTitle\": \"结果\",\n    \"shortDescription\": \"使用 Base64 对数据进行编码或解码。\",\n    \"title\": \"Base64 编码器/解码器\",\n    \"toolInfo\": {\n      \"description\": \"Base64 是一种编码方案，它将 ASCII 字符串格式的数据转换为 radix-64 表示形式。虽然 Base64 也可以用来编码字符串，但它更常用于编码二进制数据，以便在专门处理文本数据的介质上传输。\",\n      \"title\": \"什么是Base64？\"\n    }\n  },\n  \"censor\": {\n    \"description\": \"用于审查文本中单词的实用程序。在左侧的输入表单中加载您的文本，并在选项中指定所有脏话，您将立即在输出区域获得已审查的文本。\\\", longDescription: '使用此在线工具，您可以审查任何文本中的某些单词。您可以指定不想要的单词列表（例如脏话或秘密单词），程序会用替代单词替换它们，并创建可安全阅读的文本。您可以在选项中的多行文本字段中通过每行输入一个单词来指定这些单词。', keywords: ['text', 'censor', 'words', 'characters'], component: lazy(() => import('./index')), i18n: { name: 'string:censor.title', description: 'string:censor.description\",\n    \"shortDescription\": \"快速掩盖脏话或用替代词代替。\",\n    \"title\": \"文本审查\"\n  },\n  \"createPalindrome\": {\n    \"description\": \"世界上最简单的基于浏览器的回文创建工具，可从任何文本创建回文。输入文本即可立即将其转换为正反读相同的回文。非常适合文字游戏、创建对称文本模式或探索语言学知识。\",\n    \"shortDescription\": \"创建正读和倒读相同的文本\",\n    \"title\": \"创建回文\"\n  },\n  \"extractSubstring\": {\n    \"description\": \"世界上最简单的基于浏览器的文本子字符串提取工具。输入文本并指定起始和终止位置即可提取所需部分。非常适合数据处理、文本分析或从较大文本块中提取特定内容。\",\n    \"shortDescription\": \"提取指定位置之间的部分文本\",\n    \"title\": \"提取子字符串\"\n  },\n  \"hiddenCharacterDetector\": {\n    \"analysisOptions\": \"分析选项\",\n    \"category\": \"类别\",\n    \"description\": \"检测隐藏的 Unicode 字符，尤其是可能用于攻击的 RTL 覆盖字符。\",\n    \"foundChars\": \"成立 {{count}} 隐藏角色：\",\n    \"inputPlaceholder\": \"输入文本以检查隐藏字符...\",\n    \"inputTitle\": \"要分析的文本\",\n    \"invisibleChar\": \"隐形角色\",\n    \"invisibleFound\": \"发现隐形字符\",\n    \"longDescription\": \"此工具可帮助您检测文本中隐藏的 Unicode 字符，尤其是可能用于攻击的从右到左 (RTL) 覆盖字符。它可以识别隐藏字符、零宽度字符以及其他可能隐藏在看似无害的文本中的潜在恶意 Unicode 序列。\",\n    \"noHiddenChars\": \"文本中未检测到隐藏字符。\",\n    \"optionsDescription\": \"配置要检测的隐藏字符类型以及如何显示结果。\",\n    \"position\": \"位置\",\n    \"rtlAlert\": \"⚠️ 检测到 RTL 覆盖字符！此文本可能包含恶意隐藏字符。\",\n    \"rtlFound\": \"发现 RTL 覆盖\",\n    \"rtlOverride\": \"RTL 覆盖字符\",\n    \"rtlWarning\": \"警告：检测到 RTL 覆盖字符！这可能被用于攻击。\",\n    \"shortDescription\": \"查找文本中隐藏的 Unicode 字符\",\n    \"summary\": \"分析摘要\",\n    \"title\": \"隐藏字符检测器\",\n    \"totalChars\": \"隐藏角色总数： {{count}}\",\n    \"unicode\": \"Unicode\",\n    \"zeroWidthChar\": \"零宽度字符\",\n    \"zeroWidthFound\": \"发现零宽度字符\"\n  },\n  \"join\": {\n    \"blankLinesAndTrailingSpaces\": \"空行和尾随空格\",\n    \"deleteBlankDescription\": \"删除没有文本符号的行。\",\n    \"deleteBlankTitle\": \"删除空白行\",\n    \"deleteTrailingDescription\": \"删除行尾的空格和制表符。\",\n    \"deleteTrailingTitle\": \"删除尾随空格\",\n    \"description\": \"使用可自定义的分隔符将文本片段连接在一起。\",\n    \"inputTitle\": \"文本片段\",\n    \"joinCharacterDescription\": \"连接文本片段的符号。（默认为空格。）\",\n    \"joinCharacterPlaceholder\": \"加入角色\",\n    \"resultTitle\": \"连接文本\",\n    \"shortDescription\": \"使用指定的分隔符连接文本元素\",\n    \"textMergedOptions\": \"文本合并选项\",\n    \"title\": \"加入文本\",\n    \"toolInfo\": {\n      \"description\": \"使用此工具，您可以将文本的各个部分连接在一起。它接收一个以换行符分隔的文本值列表，并将它们合并在一起。您可以设置合并文本各部分之间的字符。此外，您还可以忽略所有空行，并删除所有行末的空格和制表符。Textabulous！\",\n      \"title\": \"什么是文本连接器？\"\n    }\n  },\n  \"palindrome\": {\n    \"description\": \"世界上最简单的基于浏览器的回文检测工具。即时验证文本正读和反读是否一致。非常适合字谜游戏、语言分析或验证对称文本模式。支持各种分隔符和多词回文检测。\",\n    \"shortDescription\": \"检查文本正读和反读是否相同\",\n    \"title\": \"回文\"\n  },\n  \"passwordGenerator\": {\n    \"avoidAmbiguous\": \"避免使用歧义字符（i、I、l、0、O）\",\n    \"description\": \"生成安全的随机密码，可自定义长度和字符类型。密码类型包括小写字母、大写字母、数字和特殊字符。此外，还可选择避免使用模糊字符，以提高可读性。\",\n    \"includeLowercase\": \"包含小写字母（a-z）\",\n    \"includeNumbers\": \"包含数字 (0-9)\",\n    \"includeSymbols\": \"包含特殊字符\",\n    \"includeUppercase\": \"包含大写字母（A-Z）\",\n    \"lengthDesc\": \"密码长度\",\n    \"lengthPlaceholder\": \"例如 12\",\n    \"optionsTitle\": \"密码选项\",\n    \"resultTitle\": \"生成的密码\",\n    \"shortDescription\": \"使用自定义选项生成安全的随机密码\",\n    \"title\": \"密码生成器\",\n    \"toolInfo\": {\n      \"description\": \"此工具可根据您选择的条件生成安全的随机密码。您可以自定义长度，包含或排除不同的字符类型，并避免使用模糊字符以提高可读性。非常适合为帐户、应用程序或任何安全需求创建强密码。\",\n      \"title\": \"关于密码生成器\"\n    }\n  },\n  \"quote\": {\n    \"allowDoubleQuotation\": \"允许双引号\",\n    \"description\": \"使用可自定义的选项在文本周围添加引号。\",\n    \"inputTitle\": \"输入文本\",\n    \"leftQuoteDescription\": \"左引号字符\",\n    \"processAsMultiLine\": \"作为多行文本处理\",\n    \"quoteEmptyLines\": \"引用空行\",\n    \"quoteOptions\": \"报价选项\",\n    \"resultTitle\": \"引用文本\",\n    \"rightQuoteDescription\": \"右引号字符\",\n    \"shortDescription\": \"使用各种样式在文本周围添加引号\",\n    \"title\": \"文字引述者\",\n    \"toolInfo\": {\n      \"description\": \"此工具可用于在文本周围添加引号。您可以选择不同的引号字符、处理多行文本以及控制空行的处理方式。它对于准备编程文本、格式化数据或创建风格化文本非常有用。\",\n      \"title\": \"文字引述者\"\n    }\n  },\n  \"randomizeCase\": {\n    \"description\": \"世界上最简单的基于浏览器的文本大小写随机化工具。输入文本后，即可立即将其转换为随机的大小写字母。非常适合创建独特的文本效果、测试大小写敏感度或生成丰富的文本模式。\",\n    \"shortDescription\": \"随机化文本中字母的大小写\",\n    \"title\": \"随机化案例\"\n  },\n  \"removeDuplicateLines\": {\n    \"description\": \"在左侧的输入表单中加载文本，即可立即在输出区域获得无重复行的文本。功能强大、免费且快速。加载文本行 – 获取独特的文本行\",\n    \"shortDescription\": \"快速删除文本中所有重复的行\",\n    \"title\": \"删除重复的行\"\n  },\n  \"repeat\": {\n    \"delimiterDescription\": \"输出副本的分隔符。\",\n    \"delimiterPlaceholder\": \"分隔符\",\n    \"description\": \"使用可自定义的分隔符多次重复文本。\",\n    \"inputTitle\": \"输入文本\",\n    \"numberPlaceholder\": \"数字\",\n    \"repeatAmountDescription\": \"重复次数。\",\n    \"repetitionsDelimiter\": \"重复分隔符\",\n    \"resultTitle\": \"重复的文本\",\n    \"shortDescription\": \"重复文本多次\",\n    \"textRepetitions\": \"文本重复\",\n    \"title\": \"重复文本\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您使用可选分隔符多次重复给定的文本。\",\n      \"title\": \"重复文本\"\n    }\n  },\n  \"reverse\": {\n    \"description\": \"世界上最简单的基于浏览器的文本反转工具。输入任意文本即可立即逐字符反转。非常适合创建镜像文本、分析回文或玩转文本模式。反转时保留空格和特殊字符。\",\n    \"inputTitle\": \"反转文本\",\n    \"processMultiLine\": \"处理多行文本\",\n    \"processMultiLineDescription\": \"每行将单独反转\",\n    \"resultTitle\": \"反转文本\",\n    \"reversalOptions\": \"逆转期权\",\n    \"shortDescription\": \"逐个字符地反转任何文本\",\n    \"skipEmptyLines\": \"跳过空行\",\n    \"skipEmptyLinesDescription\": \"输出中的空行将被删除\",\n    \"title\": \"撤销\",\n    \"trimWhitespace\": \"修剪空格\",\n    \"trimWhitespaceDescription\": \"删除每行的前导和尾随空格\"\n  },\n  \"rot13\": {\n    \"description\": \"使用 ROT13 密码对文本进行编码或解码。\",\n    \"inputTitle\": \"输入文本\",\n    \"resultTitle\": \"ROT13 结果\",\n    \"shortDescription\": \"使用 ROT13 密码对文本进行编码或解码。\",\n    \"title\": \"ROT13编码器/解码器\",\n    \"toolInfo\": {\n      \"description\": \"ROT13（循环13位）是一种简单的字母替换密码，用字母表中该字母后面的第13个字母替换该字母。ROT13是古罗马凯撒密码的一个特例。由于英语字母表有26个字母，因此ROT13是其自身的逆；也就是说，要撤销ROT13，需要应用相同的算法，因此编码和解码的操作相同。\",\n      \"title\": \"ROT13 是什么？\"\n    }\n  },\n  \"rotate\": {\n    \"description\": \"按指定位置旋转文本中的字符。\",\n    \"inputTitle\": \"输入文本\",\n    \"processAsMultiLine\": \"作为多行文本处理（分别旋转每一行）\",\n    \"resultTitle\": \"旋转文本\",\n    \"rotateLeft\": \"向左旋转\",\n    \"rotateRight\": \"向右旋转\",\n    \"rotationOptions\": \"旋转选项\",\n    \"shortDescription\": \"按位置移动文本中的字符。\",\n    \"stepDescription\": \"旋转位置数\",\n    \"title\": \"旋转文本\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您将字符串中的字符旋转指定位数。您可以向左或向右旋转，并通过分别旋转每一行来处理多行文本。字符串旋转对于简单的文本转换、创建模式或实现基本的加密技术非常有用。\",\n      \"title\": \"琴弦旋转\"\n    }\n  },\n  \"split\": {\n    \"charAfterChunkDescription\": \"每个块后的字符\",\n    \"charBeforeChunkDescription\": \"每个块前的字符\",\n    \"chunksDescription\": \"输出中等长数据块的数量。\",\n    \"chunksTitle\": \"使用多个块\",\n    \"description\": \"世界上最简单的基于浏览器的文本分割工具。输入文本并指定分隔符即可将其拆分为多个部分。非常适合数据处理、文本操作或从较大的文本块中提取特定内容。\",\n    \"lengthDescription\": \"每个输出块中放入的符号数量。\",\n    \"lengthTitle\": \"使用长度进行拆分\",\n    \"outputSeparatorDescription\": \"插入拆分块之间的字符。（默认为换行符“\\\\n”。）\",\n    \"outputSeparatorOptions\": \"输出分隔符选项\",\n    \"regexDescription\": \"用于将文本拆分成多个部分的正则表达式。（默认为多个空格。）\",\n    \"regexTitle\": \"使用正则表达式进行拆分\",\n    \"resultTitle\": \"文本片段\",\n    \"shortDescription\": \"使用分隔符将文本拆分为多个部分\",\n    \"splitSeparatorOptions\": \"拆分分隔符选项\",\n    \"symbolDescription\": \"用于将文本拆分成多个部分的字符。（默认为空格。）\",\n    \"symbolTitle\": \"使用符号进行拆分\",\n    \"title\": \"分裂\"\n  },\n  \"statistic\": {\n    \"characterFrequencyAnalysis\": \"字频分析\",\n    \"characterFrequencyAnalysisDescription\": \"计算每个字符在文本中出现的频率\",\n    \"delimitersOptions\": \"分隔符选项\",\n    \"description\": \"分析文本并生成综合统计数据。\",\n    \"includeEmptyLines\": \"包含空行\",\n    \"includeEmptyLinesDescription\": \"计算行数时包含空行\",\n    \"inputTitle\": \"输入文本\",\n    \"resultTitle\": \"文本统计\",\n    \"sentenceDelimitersDescription\": \"输入用于分隔您语言中的句子的自定义字符（以逗号分隔）或将其留空作为默认设置。\",\n    \"sentenceDelimitersPlaceholder\": \"例如。，！，？，...\",\n    \"shortDescription\": \"获取有关文本的统计数据\",\n    \"statisticsOptions\": \"统计选项\",\n    \"title\": \"文本统计\",\n    \"toolInfo\": {\n      \"description\": \"该工具允许您分析文本并生成全面的统计数据，包括字符数、字数、行数以及字符和单词的频率分析。\",\n      \"title\": \"什么是 {{title}}？\"\n    },\n    \"wordDelimitersDescription\": \"输入自定义正则表达式来计算单词数，或将其留空作为默认值。\",\n    \"wordDelimitersPlaceholder\": \"例如。 \\\\s.,;:!?”«»()…\",\n    \"wordFrequencyAnalysis\": \"词频分析\",\n    \"wordFrequencyAnalysisDescription\": \"计算每个单词在文本中出现的频率\"\n  },\n  \"textReplacer\": {\n    \"description\": \"用新内容替换文本模式。\",\n    \"findPatternInText\": \"在文本中查找此模式\",\n    \"findPatternUsingRegexp\": \"使用正则表达式查找模式\",\n    \"inputTitle\": \"要替换的文本\",\n    \"newTextPlaceholder\": \"新文本\",\n    \"regexpDescription\": \"输入要替换的正则表达式。\",\n    \"replacePatternDescription\": \"输入用于替换的模式。\",\n    \"replaceText\": \"替换文本\",\n    \"resultTitle\": \"带替换的文本\",\n    \"searchPatternDescription\": \"输入您想要替换的文本模式。\",\n    \"searchText\": \"搜索文本\",\n    \"shortDescription\": \"快速替换内容中的文本\",\n    \"title\": \"文本替换器\",\n    \"toolInfo\": {\n      \"description\": \"使用这款基于浏览器的简单工具，轻松替换内容中的特定文本。只需输入文本，设置要替换的文本和替换值，即可立即获取更新版本。\",\n      \"title\": \"文本替换器\"\n    }\n  },\n  \"toMorse\": {\n    \"dashSymbolDescription\": \"与摩尔斯电码中的破折号相对应的符号。\",\n    \"description\": \"将文本转换为摩尔斯电码。\",\n    \"dotSymbolDescription\": \"与摩尔斯电码中的点对应的符号。\",\n    \"longSignal\": \"长信号\",\n    \"resultTitle\": \"摩尔斯电码\",\n    \"shortDescription\": \"快速将文本编码为摩尔斯电码\",\n    \"shortSignal\": \"短信号\",\n    \"title\": \"字符串到莫尔斯\"\n  },\n  \"truncate\": {\n    \"addTruncationIndicator\": \"添加截断指示符\",\n    \"charactersPlaceholder\": \"人物\",\n    \"description\": \"将文本缩短至指定长度。\",\n    \"indicatorDescription\": \"在文本末尾（或开头）添加的字符。注意：这些字符计入长度。\",\n    \"inputTitle\": \"输入文本\",\n    \"leftSideDescription\": \"从文本开头删除字符。\",\n    \"leftSideTruncation\": \"左侧截断\",\n    \"lengthAndLines\": \"长度和线条\",\n    \"lineByLineDescription\": \"分别截断每一行。\",\n    \"lineByLineTruncating\": \"逐行截断\",\n    \"maxLengthDescription\": \"文本中保留的字符数。\",\n    \"numberPlaceholder\": \"数字\",\n    \"resultTitle\": \"文本被截断\",\n    \"rightSideDescription\": \"删除文本末尾的字符。\",\n    \"rightSideTruncation\": \"右侧截断\",\n    \"shortDescription\": \"将文本截断为指定长度\",\n    \"suffixAndAffix\": \"后缀和词缀\",\n    \"title\": \"截断文本\",\n    \"toolInfo\": {\n      \"description\": \"在左侧的输入表单中加载您的文本，您将自动在右侧获得截断文本。\",\n      \"title\": \"截断文本\"\n    },\n    \"truncationSide\": \"截断侧\"\n  },\n  \"uppercase\": {\n    \"description\": \"将文本转换为大写字母。\",\n    \"inputTitle\": \"输入文本\",\n    \"resultTitle\": \"大写文本\",\n    \"shortDescription\": \"将文本转换为大写\",\n    \"title\": \"转换为大写\"\n  },\n  \"urlDecode\": {\n    \"inputTitle\": \"输入字符串（URL 转义）\",\n    \"resultTitle\": \"输出字符串\",\n    \"toolInfo\": {\n      \"description\": \"加载您的字符串，它将自动获得 URL 非转义。\",\n      \"longDescription\": \"此工具用于对先前 URL 编码的字符串进行 URL 解码。URL 解码是 URL 编码的逆操作。所有百分号编码的字符都会被解码为您可以理解的字符。一些最常用的百分号编码值包括：%20 表示空格，%3a 表示冒号，%2f 表示斜杠，%3f 表示问号。百分号后面的两位数字是该字符的十六进制字符码值。\",\n      \"shortDescription\": \"快速对字符串进行 URL 转义。\",\n      \"title\": \"字符串 URL 解码器\"\n    }\n  },\n  \"urlEncode\": {\n    \"encodingOption\": {\n      \"nonSpecialCharDescription\": \"如果选中，则输入字符串中的所有字符都将转换为 URL 编码（不仅仅是特殊字符）。\",\n      \"nonSpecialCharPlaceholder\": \"对非特殊字符进行编码\",\n      \"title\": \"编码选项\"\n    },\n    \"inputTitle\": \"输入字符串\",\n    \"resultTitle\": \"URL转义字符串\",\n    \"toolInfo\": {\n      \"description\": \"加载您的字符串，它将自动进行 URL 转义。\",\n      \"longDescription\": \"此工具会对字符串进行 URL 编码。特殊 URL 字符会被转换为百分号编码。这种编码之所以称为百分号编码，是因为每个字符的数值都会被转换为百分号，后跟两位十六进制值。十六进制值根据字符的代码点值确定。例如，空格会被转义为 %20，冒号会被转义为 %3a，斜杠会被转义为 %2f。非特殊字符保持不变。如果您还需要将非特殊字符转换为百分号编码，我们还添加了一个额外的选项来实现此目的。选择“encode-non-special-chars”选项即可启用此行为。\",\n      \"shortDescription\": \"快速对字符串进行 URL 转义。\",\n      \"title\": \"字符串 URL 编码器\"\n    }\n  },\n  \"unicode\": {\n    \"title\": \"Unicode 编码器 / 解码器\",\n    \"inputTitle\": \"输入\",\n    \"resultTitle\": \"处理结果\",\n    \"optionsTitle\": \"模式\",\n    \"caseOptionsTitle\": \"大小写选项\",\n    \"encode\": \"编码\",\n    \"decode\": \"解码\",\n    \"uppercase\": \"大写十六进制\",\n    \"description\": \"将文本转换为 Unicode 转义序列，或将其解码回可读文本。\",\n    \"shortDescription\": \"使用 Unicode 转义序列对文本进行编码或解码。\",\n    \"toolInfo\": {\n      \"title\": \"Unicode 编码器 / 解码器\",\n      \"description\": \"此工具可让您将纯文本转换为 Unicode 转义序列（例如，\\\\uXXXX），以及将 Unicode 转义序列解码回标准文本。您还可以选择编码时十六进制输出以大写或小写格式显示。\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/time.json",
    "content": "{\n  \"checkLeapYears\": {\n    \"description\": \"检查某一年份是否为闰年并获取闰年信息。\",\n    \"exampleDescription\": \"我们的一位朋友出生在闰年，也就是2月29日，因此她每四年才过一次生日。由于我们永远无法记住她的生日，所以我们用程序创建了一个即将到来的闰年的提醒列表。为了创建她接下来的生日列表，我们将从2025年到2040年的一系列年份加载到输入中，并获取每年的状态。如果程序显示是闰年，那么我们就知道我们会在2月29日被邀请参加一个生日派对。\",\n    \"exampleTitle\": \"查找 2 月 29 日的生日\",\n    \"inputTitle\": \"输入年份\",\n    \"resultTitle\": \"闰年结果\",\n    \"shortDescription\": \"检查年份是否为闰年\",\n    \"title\": \"检查闰年\",\n    \"toolInfo\": {\n      \"description\": \"闰年是指在每年的2月29日增加一天，以使日历年与天文年保持一致。闰年每4年一次，但能被100整除但不能被400整除的年份除外。\",\n      \"title\": \"什么是闰年？\"\n    }\n  },\n  \"convertDaysToHours\": {\n    \"addHoursName\": \"添加营业时间名称\",\n    \"addHoursNameDescription\": \"将字符串 hours 附加到输出值\",\n    \"description\": \"使用可自定义的选项将天数转换为小时数。\",\n    \"hoursName\": \"营业时间名称\",\n    \"shortDescription\": \"将天数转换为小时数\",\n    \"title\": \"将天数转换为小时数\",\n    \"toolInfo\": {\n      \"description\": \"此工具可让您将天数转换为小时数。您可以输入数字或单位形式的天数，该工具会将其转换为小时数。您还可以选择在输出值后附加“小时”后缀。\",\n      \"title\": \"将天数转换为小时数\"\n    }\n  },\n  \"convertHoursToDays\": {\n    \"addDaysName\": \"添加日期名称\",\n    \"addDaysNameDescription\": \"将字符串 days 附加到输出值\",\n    \"daysName\": \"日期名称\",\n    \"description\": \"使用可自定义的选项将小时转换为天。\",\n    \"shortDescription\": \"将小时转换为天\",\n    \"title\": \"将小时转换为天\",\n    \"toolInfo\": {\n      \"description\": \"此工具可让您将小时转换为天。您可以输入数字或单位形式的小时，该工具会将其转换为天。您还可以选择在输出值后附加“天”后缀。\",\n      \"title\": \"将小时转换为天\"\n    }\n  },\n  \"convertSecondsToTime\": {\n    \"addPadding\": \"添加填充\",\n    \"addPaddingDescription\": \"在小时、分钟和秒后添加零填充。\",\n    \"description\": \"将秒转换为可读的时间格式（小时:分钟:秒）。输入秒数即可获取格式化的时间。\",\n    \"shortDescription\": \"将秒数转换为时间格式\",\n    \"timePadding\": \"时间填充\",\n    \"title\": \"将秒转换为时间\",\n    \"toolInfo\": {\n      \"title\": \"什么是 {{title}}？\"\n    }\n  },\n  \"convertTimeToSeconds\": {\n    \"description\": \"将格式化的时间（HH：MM：SS）转换为秒。\",\n    \"inputTitle\": \"输入时间\",\n    \"resultTitle\": \"秒\",\n    \"shortDescription\": \"将时间格式转换为秒\",\n    \"title\": \"将时间转换为秒\",\n    \"toolInfo\": {\n      \"description\": \"此工具可让您将格式化的时间字符串 (HH:MM:SS) 转换为秒。它对于计算持续时间和时间间隔非常有用。\",\n      \"title\": \"将时间转换为秒\"\n    }\n  },\n  \"convertUnixToDate\": {\n    \"addUtcLabel\": \"添加“UTC”后缀\",\n    \"addUtcLabelDescription\": \"转换后的日期后显示“UTC”（仅适用于 UTC 模式）\",\n    \"description\": \"将 Unix 时间戳转换为人类可读的日期。\",\n    \"outputOptions\": \"输出选项\",\n    \"shortDescription\": \"将 Unix 时间戳转换为日期\",\n    \"title\": \"将 Unix 转换为日期\",\n    \"toolInfo\": {\n      \"description\": \"此工具可将 Unix 时间戳（以秒为单位）转换为人类可读的日期格式（例如 YYYY-MM-DD HH:MM:SS）。它支持本地和 UTC 输出，因此可用于快速解释来自日志、API 或使用 Unix 时间的系统的时间戳。\",\n      \"title\": \"将 Unix 转换为日期\"\n    },\n    \"useLocalTime\": \"使用当地时间\",\n    \"useLocalTimeDescription\": \"显示以当地时区而非 UTC 转换的日期\",\n    \"withLabel\": \"选项\"\n  },\n  \"crontabGuru\": {\n    \"description\": \"生成并理解 cron 表达式。为自动化任务和系统作业创建 cron 计划。\",\n    \"shortDescription\": \"生成并理解 cron 表达式\",\n    \"title\": \"Crontab 大师\"\n  },\n  \"timeBetweenDates\": {\n    \"description\": \"计算两个日期之间的时差。获取精确的天、小时、分钟和秒数。\",\n    \"endDate\": \"结束日期\",\n    \"endDateTime\": \"结束日期和时间\",\n    \"endTime\": \"结束时间\",\n    \"endTimezone\": \"结束时区\",\n    \"shortDescription\": \"计算两个日期之间的时间\",\n    \"startDate\": \"开始日期\",\n    \"startDateTime\": \"开始日期和时间\",\n    \"startTime\": \"开始时间\",\n    \"startTimezone\": \"开始时区\",\n    \"title\": \"日期间隔时间\",\n    \"toolInfo\": {\n      \"description\": \"计算两个日期和时间之间的精确时差，支持不同时区。此工具提供按不同单位（年、月、日、时、分、秒）计算的详细时差细分。\",\n      \"title\": \"日期间隔时间计算器\"\n    }\n  },\n  \"truncateClockTime\": {\n    \"description\": \"截断时钟时间以删除秒数或分钟数。将时间四舍五入到最接近的小时数、分钟数或自定义间隔。\",\n    \"printDroppedComponents\": \"打印掉落的组件\",\n    \"shortDescription\": \"将时钟时间截断为指定的精度\",\n    \"timePadding\": \"时间填充\",\n    \"title\": \"截断时钟时间\",\n    \"toolInfo\": {\n      \"title\": \"什么是 {{title}}？\"\n    },\n    \"truncateMinutesAndSeconds\": \"截断分钟和秒\",\n    \"truncateMinutesAndSecondsDescription\": \"删除每个时钟时间中的分钟和秒部分。\",\n    \"truncateOnlySeconds\": \"仅截断秒数\",\n    \"truncateOnlySecondsDescription\": \"从每个时钟时间中删除秒数部分。\",\n    \"truncationSide\": \"截断侧\",\n    \"useZeroPadding\": \"使用零填充\",\n    \"zeroPaddingDescription\": \"使所有时间部分始终为两位数宽度。\",\n    \"zeroPrintDescription\": \"将删除的部分显示为零值“00”。\",\n    \"zeroPrintTruncatedParts\": \"零打印截断部件\"\n  },\n  \"convertTimeToDecimal\": {\n    \"title\": \"把时间转换至十进制格式\",\n    \"description\": \"将格式化的时间长度（HH:MM:SS）转换为小数小时值。\",\n    \"shortDescription\": \"把时段转化为小数小时值\",\n    \"longDescription\": \"将格式化的时间字符串（HH:MM:SS 或 HH:MM）转换为其等效的小数小时值。小时可以是任意正数，而分钟和秒接受 0-59 之间的值，可以是一位或两位数字（例如，'12:5' 或 '12:05'）。此函数解析小时、分钟和秒，然后将总时长计算为单一小数值。它适用于生产力追踪、薪资计算、按时计费、数据分析，或任何需要将人类可读时间转换为易于求和、比较或处理的数值格式的工作流程。例如，'03:26:00' 转换为 3.43 小时，'12:5' 转换为 12.08 小时。\"\n  },\n  \"discordTimestamp\": {\n    \"name\": \"Discord 时间戳生成器\",\n    \"description\": \"将日期和时间转换为 Discord 的特殊时间戳格式，自动适应每位用户的本地时区和语言\",\n    \"shortDescription\": \"从日期生成 Discord 时间戳\",\n    \"inputTitle\": \"输入日期时间\",\n    \"outputTitle\": \"Discord 时间戳\",\n    \"formats\": {\n      \"title\": \"时间戳格式\",\n      \"description\": \"选择时间戳在 Discord 中的显示方式 — 悬停在每个选项上可查看示例\",\n      \"short_time\": \"短时间（例：14:30）\",\n      \"long_time\": \"长时间（例：14:30:00）\",\n      \"short_date\": \"短日期（例：01/01/2025）\",\n      \"long_date\": \"长日期（例：2025年1月1日）\",\n      \"short_datetime\": \"短日期和时间（例：2025年1月1日 14:30）\",\n      \"long_datetime\": \"长日期和时间（例：2025年1月1日 星期一 14:30）\",\n      \"relative\": \"相对时间（例：2小时前）\"\n    },\n    \"utc\": {\n      \"title\": \"输入选项\",\n      \"label\": \"强制使用 UTC\",\n      \"description\": \"将输入的日期时间视为 UTC。禁用则使用您的本地时区\"\n    },\n    \"toolInfo\": {\n      \"title\": \"什么是 Discord 时间戳？\",\n      \"description\": \"Discord 时间戳是一种动态显示日期和时间的特殊语法。它不显示固定时间，而是自动转换为每位查看者的本地时区和语言 — 让每个人都能看到适合其所在位置的正确时间。只需每行输入一个日期时间，选择格式，然后将结果直接粘贴到 Discord 中。该工具独立处理每一行 — 空行在输出中保留，无效行以 ❌ 标记，有效的日期时间即时转换。启用 UTC 模式可确保结果不受本地时区影响。\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/translation.json",
    "content": "{\n  \"audio\": {\n    \"changeSpeed\": {\n      \"description\": \"更改音频文件的播放速度。在保持音调不变的情况下加快或减慢音频速度。\",\n      \"name\": \"更改音频速度\",\n      \"shortDescription\": \"更改音频文件的速度\"\n    },\n    \"extractAudio\": {\n      \"description\": \"从视频文件中提取音轨并将其保存为您选择的格式（AAC、MP3、WAV）的单独音频文件。\",\n      \"name\": \"提取音频\",\n      \"shortDescription\": \"将视频文件（MP4、MOV 等）中的音频提取为 AAC、MP3 或 WAV。\"\n    }\n  },\n  \"baseFileInput\": {\n    \"copyFailed\": \"复制失败： {{error}}\",\n    \"dropFileHere\": \"放下你的 {{type}} 这里\",\n    \"fileCopied\": \"文件已复制\",\n    \"selectFileDescription\": \"单击此处选择一个 {{type}} 在您的设备上，按 Ctrl+V 使用 {{type}} 从剪贴板中，或从桌面拖放文件\"\n  },\n  \"categories\": {\n    \"audio\": {\n      \"description\": \"处理音频的工具——从视频中提取音频、调整音频速度、合并多个音频文件等等。\",\n      \"title\": \"音频工具\"\n    },\n    \"converters\": {\n      \"description\": \"用于在不同格式之间转换数据的工具 — 转换图片、音频、视频、文本等。\",\n      \"title\": \"转换工具\"\n    },\n    \"csv\": {\n      \"description\": \"处理 CSV 文件的工具 - 将 CSV 转换为不同的格式、操作 CSV 数据、验证 CSV 结构以及有效地处理 CSV 文件。\",\n      \"title\": \"CSV 工具\"\n    },\n    \"gif\": {\n      \"description\": \"处理 GIF 动画的工具 - 创建透明 GIF、提取 GIF 帧、向 GIF 添加文本、裁剪、旋转、反转 GIF 等等。\",\n      \"title\": \"GIF工具\"\n    },\n    \"image-generic\": {\n      \"description\": \"处理图片的工具——压缩、调整大小、裁剪、转换为 JPG、旋转、删除背景等等。\",\n      \"title\": \"图像工具\"\n    },\n    \"json\": {\n      \"description\": \"处理 JSON 数据结构的工具——美化和缩小 JSON 对象、展平 JSON 数组、字符串化 JSON 值、分析数据等等\",\n      \"title\": \"JSON 工具\"\n    },\n    \"list\": {\n      \"description\": \"处理列表的工具——排序、反转、随机化列表、查找唯一和重复的列表项、更改列表项分隔符等等。\",\n      \"title\": \"列表工具\"\n    },\n    \"number\": {\n      \"description\": \"处理数字的工具——生成数字序列、将数字转换为文字、将文字转换为数字、排序、舍入、因式分解等等。\",\n      \"title\": \"数字工具\"\n    },\n    \"pdf\": {\n      \"description\": \"处理 PDF 文件的工具 - 从 PDF 中提取文本、将 PDF 转换为其他格式、操作 PDF 等等。\",\n      \"title\": \"PDF工具\"\n    },\n    \"png\": {\n      \"description\": \"处理 PNG 图像的工具 - 将 PNG 转换为 JPG、创建透明 PNG、更改 PNG 颜色、裁剪、旋转、调整 PNG 大小等等。\",\n      \"title\": \"PNG工具\"\n    },\n    \"seeAll\": \"查看全部 {{title}}\",\n    \"string\": {\n      \"description\": \"处理文本的工具——将文本转换为图像、查找和替换文本、将文本拆分成片段、连接文本行、重复文本等等。\",\n      \"title\": \"文本工具\"\n    },\n    \"time\": {\n      \"description\": \"处理时间和日期的工具——计算时差、在时区之间转换、格式化日期、生成日期序列等等。\",\n      \"title\": \"时间工具\"\n    },\n    \"try\": \"尝试 {{title}}\",\n    \"video\": {\n      \"description\": \"处理视频的工具——从视频中提取帧、从视频创建 GIF、将视频转换为不同的格式等等。\",\n      \"title\": \"视频工具\"\n    },\n    \"xml\": {\n      \"description\": \"处理 XML 数据结构的工具 - 查看器、美化器、验证器等等\",\n      \"title\": \"XML 工具\"\n    }\n  },\n\n  \"csv\": {\n    \"findIncompleteCsvRecords\": {\n      \"description\": \"只需将您的 CSV 文件上传到下方表单，此工具就会自动检查所有行或列是否均缺失值。在工具选项中，您可以调整输入文件格式（指定分隔符、引号和注释符）。此外，您还可以启用空值检查、跳过空行，以及设置输出中错误消息的数量限制。\",\n      \"name\": \"查找不完整的 CSV 记录\",\n      \"shortDescription\": \"快速查找 CSV 中缺少值的行和列。\"\n    }\n  },\n  \"hero\": {\n    \"brand\": \"OmniTools\",\n    \"description\": \"使用 OmniTools 提升您的工作效率，这是一款快速完成任务的终极工具包！数千个用户友好的实用程序，可直接通过浏览器编辑图像、文本、列表和数据。\",\n    \"examples\": {\n      \"calculateNumberSum\": \"计算数字和\",\n      \"changeGifSpeed\": \"更改 GIF 速度\",\n      \"compressPng\": \"压缩 PNG\",\n      \"createTransparentImage\": \"创建透明图像\",\n      \"prettifyJson\": \"美化 JSON\",\n      \"sortList\": \"对列表进行排序\",\n      \"splitPdf\": \"拆分 PDF\",\n      \"splitText\": \"拆分文本\",\n      \"trimVideo\": \"修剪视频\"\n    },\n    \"searchPlaceholder\": \"搜索所有工具\",\n    \"title\": \"快速完成工作\"\n  },\n  \"inputFooter\": {\n    \"clear\": \"清除\",\n    \"copyToClipboard\": \"复制到剪贴板\",\n    \"importFromFile\": \"从文件导入\"\n  },\n  \"list\": {\n    \"group\": {\n      \"description\": \"世界上最简单的基于浏览器的列表项分组工具。输入您的列表并指定分组条件，即可将项目按逻辑分组。非常适合分类数据、组织信息或创建结构化列表。支持自定义分隔符和各种分组选项。\",\n      \"name\": \"团体\",\n      \"shortDescription\": \"按共同属性对列表项进行分组\"\n    },\n    \"reverse\": {\n      \"description\": \"这是一个基于浏览器的超级简单的应用程序，可以反向打印所有列表项。输入项可以用任意符号分隔，并且您还可以更改反向列表项的分隔符。\",\n      \"name\": \"撤销\",\n      \"shortDescription\": \"快速反转列表\"\n    },\n    \"sort\": {\n      \"description\": \"这是一款基于浏览器的超级简单应用程序，可以对列表中的项目进行排序，并按升序或降序排列。您可以按字母顺序、数字或长度对项目进行排序。您还可以删除重复和空白的项目，以及修剪周围有空格的单个项目。您可以使用任何分隔符分隔输入列表项，也可以使用正则表达式分隔它们。此外，您还可以为排序后的输出列表创建新的分隔符。\",\n      \"name\": \"种类\",\n      \"shortDescription\": \"快速对列表进行排序\"\n    }\n  },\n  \"navbar\": {\n    \"buyMeACoffee\": \"请我喝杯咖啡\",\n    \"hireMe\": \"雇用我\",\n    \"home\": \"家\",\n    \"tools\": \"工具\"\n  },\n  \"number\": {\n    \"generate\": {\n      \"description\": \"在浏览器中快速计算整数列表。要获取列表，只需指定第一个整数，并在下方选项中更改值和总数，此实用程序就会生成相应数量的整数。\",\n      \"name\": \"生成数字\",\n      \"shortDescription\": \"在浏览器中快速计算整数列表\"\n    },\n    \"sum\": {\n      \"description\": \"这是一款基于浏览器的超级简单的求和应用程序。输入的数字可以用任意符号分隔，并且您还可以更改求和结果的分隔符。\",\n      \"name\": \"总和数\",\n      \"shortDescription\": \"快速对数字列表求和\"\n    }\n  },\n  \"numericInputWithUnit\": {\n    \"unit\": \"单元\"\n  },\n  \"pdf\": {\n    \"compressPdf\": {\n      \"description\": \"使用 Ghostscript 减小 PDF 文件大小同时保持质量\",\n      \"name\": \"压缩PDF\",\n      \"shortDescription\": \"在浏览器中安全地压缩 PDF 文件\"\n    },\n    \"mergePdf\": {\n      \"description\": \"合并多个 PDF 文件为一个文档。\",\n      \"name\": \"合并 PDF\",\n      \"shortDescription\": \"将多个 PDF 文件合并为一个文档\"\n    },\n    \"pdfToEpub\": {\n      \"description\": \"将 PDF 文档转换为 EPUB 文件，以实现更好的电子阅读器兼容性。\",\n      \"name\": \"PDF 转 EPUB\",\n      \"shortDescription\": \"将 PDF 文件转换为 EPUB 格式\"\n    },\n    \"protectPdf\": {\n      \"description\": \"在浏览器中安全地为 PDF 文件添加密码保护\",\n      \"name\": \"保护 PDF\",\n      \"shortDescription\": \"使用密码安全地保护 PDF 文件\"\n    },\n    \"splitPdf\": {\n      \"description\": \"使用页码或范围（例如 1,5-8）从 PDF 文件中提取特定页面\",\n      \"name\": \"拆分 PDF\",\n      \"shortDescription\": \"从 PDF 文件中提取特定页面\"\n    }\n  },\n  \"resultFooter\": {\n    \"copy\": \"复制到剪贴板\",\n    \"download\": \"下载\"\n  },\n  \"string\": {\n    \"createPalindrome\": {\n      \"description\": \"世界上最简单的基于浏览器的回文创建工具，可从任何文本创建回文。输入文本即可立即将其转换为正反读相同的回文。非常适合文字游戏、创建对称文本模式或探索语言学知识。\",\n      \"name\": \"创建回文\",\n      \"shortDescription\": \"创建正读和倒读相同的文本\"\n    },\n    \"palindrome\": {\n      \"description\": \"世界上最简单的基于浏览器的回文检测工具。即时验证文本正读和反读是否一致。非常适合字谜游戏、语言分析或验证对称文本模式。支持各种分隔符和多词回文检测。\",\n      \"name\": \"回文\",\n      \"shortDescription\": \"检查文本正读和反读是否相同\"\n    },\n    \"repeat\": {\n      \"description\": \"此工具允许您使用可选分隔符多次重复给定的文本。\",\n      \"name\": \"重复文本\",\n      \"shortDescription\": \"重复文本多次\"\n    },\n    \"reverse\": {\n      \"description\": \"世界上最简单的基于浏览器的文本反转工具。输入任意文本即可立即逐字符反转。非常适合创建镜像文本、分析回文或玩转文本模式。反转时保留空格和特殊字符。\",\n      \"name\": \"撤销\",\n      \"shortDescription\": \"逐个字符地反转任何文本\"\n    },\n    \"toMorse\": {\n      \"description\": \"世界上最简单的基于浏览器的文本转摩尔斯电码工具。只需在左侧输入框中输入文本，即可立即在输出区域获得摩尔斯电码。功能强大、免费且快速。输入文本，即可获得摩尔斯电码。\",\n      \"name\": \"字符串到莫尔斯\",\n      \"shortDescription\": \"快速将文本编码为摩尔斯电码\"\n    },\n    \"uppercase\": {\n      \"description\": \"世界上最简单的基于浏览器的大写文本转换工具。只需输入文本，即可自动转换为全部大写字母。非常适合创建标题、强调文本或标准化文本格式。支持各种文本格式并保留特殊字符。\",\n      \"name\": \"大写\",\n      \"shortDescription\": \"将文本转换为大写字母\"\n    }\n  },\n  \"toolExamples\": {\n    \"subtitle\": \"点击尝试！\",\n    \"title\": \"{{title}} 示例\"\n  },\n  \"toolFileResult\": {\n    \"copied\": \"文件已复制\",\n    \"copyFailed\": \"复制失败： {{error}}\",\n    \"loading\": \"正在加载...这可能需要一点时间。\",\n    \"result\": \"结果\"\n  },\n  \"toolHeader\": {\n    \"seeExamples\": \"查看示例\"\n  },\n  \"toolLayout\": {\n    \"allToolsTitle\": \"全部 {{type}}\"\n  },\n  \"toolMultiFileResult\": {\n    \"copied\": \"文件已复制\",\n    \"copyFailed\": \"复制失败： {{error}}\",\n    \"loading\": \"正在加载...这可能需要一点时间。\",\n    \"result\": \"结果\"\n  },\n  \"toolMultipleAudioInput\": {\n    \"inputTitle\": \"输入 {{type}}\",\n    \"noFilesSelected\": \"未选择任何文件\"\n  },\n  \"toolMultiplePdfInput\": {\n    \"inputTitle\": \"输入 {{type}}\",\n    \"noFilesSelected\": \"未选择任何文件\"\n  },\n  \"toolOptions\": {\n    \"title\": \"工具选项\"\n  },\n  \"toolTextInput\": {\n    \"copied\": \"文本已复制\",\n    \"copyFailed\": \"复制失败： {{error}}\",\n    \"input\": \"输入文本\",\n    \"placeholder\": \"在此处输入您的文本...\"\n  },\n  \"toolTextResult\": {\n    \"copied\": \"文本已复制\",\n    \"copyFailed\": \"复制失败： {{error}}\",\n    \"loading\": \"正在加载...这可能需要一点时间。\",\n    \"result\": \"结果\"\n  },\n  \"userTypes\": {\n    \"developers\": \"开发人员\",\n    \"generalUsers\": \"普通用户\"\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/video.json",
    "content": "{\n  \"changeSpeed\": {\n    \"defaultMultiplier\": \"默认乘数：2 表示速度快 2 倍\",\n    \"description\": \"更改视频文件的播放速度。在保持音频同步的同时，加快或减慢视频速度。支持各种倍速器和常见视频格式。\",\n    \"inputTitle\": \"输入视频\",\n    \"newVideoSpeed\": \"新视频速度\",\n    \"resultTitle\": \"编辑视频\",\n    \"settingSpeed\": \"设定速度\",\n    \"shortDescription\": \"更改视频播放速度\",\n    \"title\": \"更改视频速度\",\n    \"toolInfo\": {\n      \"title\": \"什么是 {{title}}？\"\n    }\n  },\n  \"compress\": {\n    \"default\": \"默认\",\n    \"description\": \"通过将视频缩放到不同的分辨率（例如 240p、480p、720p 等）来压缩视频。此工具有助于在保持可接受质量的同时减小文件大小。支持 MP4、WebM 和 OGG 等常见视频格式。\",\n    \"inputTitle\": \"输入视频\",\n    \"loadingText\": \"正在压缩视频...\",\n    \"lossless\": \"无损\",\n    \"quality\": \"质量（CRF）\",\n    \"resolution\": \"解决\",\n    \"resultTitle\": \"压缩视频\",\n    \"shortDescription\": \"通过缩放到不同的分辨率来压缩视频\",\n    \"title\": \"压缩视频\",\n    \"worst\": \"最差\"\n  },\n  \"cropVideo\": {\n    \"cropCoordinates\": \"作物坐标\",\n    \"croppingVideo\": \"裁剪视频\",\n    \"description\": \"裁剪视频以删除不需要的区域。\",\n    \"errorBeyondHeight\": \"裁剪区域超出视频高度（{{height}}像素）\",\n    \"errorBeyondWidth\": \"裁剪区域超出视频宽度（{{width}}像素）\",\n    \"errorCroppingVideo\": \"裁剪视频时出错。请检查参数和视频文件。\",\n    \"errorLoadingDimensions\": \"无法加载视频尺寸\",\n    \"errorNonNegativeCoordinates\": \"X 和 Y 坐标必须为非负数\",\n    \"errorPositiveDimensions\": \"宽度和高度必须为正数\",\n    \"height\": \"高度\",\n    \"inputTitle\": \"输入视频\",\n    \"loadVideoForDimensions\": \"加载视频以查看尺寸\",\n    \"longDescription\": \"此工具可让您裁剪视频文件，去除不需要的区域或聚焦视频的特定部分。它可用于去除黑条、调整宽高比或聚焦重要内容。支持多种视频格式，包括 MP4、MOV 和 AVI。\",\n    \"resultTitle\": \"裁剪视频\",\n    \"shortDescription\": \"裁剪视频以删除不需要的区域\",\n    \"title\": \"裁剪视频\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您裁剪视频文件以删除不需要的区域。您可以通过设置 X、Y 坐标以及宽度和高度尺寸来指定裁剪区域。\",\n      \"title\": \"裁剪视频\"\n    },\n    \"videoDimensions\": \"视频尺寸： {{width}} × {{height}} 像素\",\n    \"videoInformation\": \"视频信息\",\n    \"width\": \"宽度\",\n    \"xCoordinate\": \"X（左）\",\n    \"yCoordinate\": \"Y（顶部）\"\n  },\n  \"flip\": {\n    \"description\": \"水平或垂直翻转视频文件。镜像视频以获得特殊效果或纠正方向问题。\",\n    \"flippingVideo\": \"翻转视频\",\n    \"horizontalLabel\": \"水平（镜像）\",\n    \"inputTitle\": \"输入视频\",\n    \"orientation\": \"方向\",\n    \"resultTitle\": \"翻转视频\",\n    \"shortDescription\": \"水平或垂直翻转视频\",\n    \"title\": \"翻转视频\",\n    \"verticalLabel\": \"垂直（倒置）\"\n  },\n  \"gif\": {\n    \"changeSpeed\": {\n      \"description\": \"更改 GIF 动画的播放速度。在保持动画流畅的情况下，加快或减慢 GIF 的速度。\",\n      \"shortDescription\": \"更改 GIF 动画速度\",\n      \"title\": \"更改 GIF 速度\"\n    }\n  },\n  \"loop\": {\n    \"description\": \"通过多次重复播放原始视频来创建循环视频。\",\n    \"inputTitle\": \"输入视频\",\n    \"loopingVideo\": \"循环视频\",\n    \"loops\": \"循环\",\n    \"numberOfLoops\": \"循环次数\",\n    \"resultTitle\": \"循环视频\",\n    \"shortDescription\": \"创建循环视频文件\",\n    \"title\": \"循环视频\",\n    \"toolInfo\": {\n      \"description\": \"此工具可让您通过多次重复播放原始视频来创建循环视频。您可以指定视频循环播放的次数。\",\n      \"title\": \"什么是 {{title}}？\"\n    }\n  },\n  \"mergeVideo\": {\n    \"description\": \"将多个视频文件合并为一个连续的视频。\",\n    \"longDescription\": \"此工具允许您将多个视频文件合并或附加为一个连续的视频。只需上传您的视频文件，按所需顺序排列，然后将它们合并为一个文件，即可轻松共享或编辑。\",\n    \"shortDescription\": \"轻松附加和合并视频。\",\n    \"title\": \"合并视频\"\n  },\n  \"rotate\": {\n    \"180Degrees\": \"180°（上下颠倒）\",\n    \"270Degrees\": \"270°（逆时针90°）\",\n    \"90Degrees\": \"顺时针 90°\",\n    \"description\": \"将视频文件旋转 90 度、180 度或 270 度。通过精确的旋转控制来校正视频方向或创建特殊效果。\",\n    \"inputTitle\": \"输入视频\",\n    \"resultTitle\": \"旋转视频\",\n    \"rotatingVideo\": \"旋转视频\",\n    \"rotation\": \"旋转\",\n    \"shortDescription\": \"按指定角度旋转视频\",\n    \"title\": \"旋转视频\"\n  },\n  \"trim\": {\n    \"description\": \"通过指定开始和结束时间来修剪视频文件。从视频的开头或结尾删除不需要的部分。\",\n    \"endTime\": \"结束时间\",\n    \"inputTitle\": \"输入视频\",\n    \"resultTitle\": \"修剪的视频\",\n    \"shortDescription\": \"通过删除不需要的部分来修剪视频\",\n    \"startTime\": \"开始时间\",\n    \"timestamps\": \"时间戳\",\n    \"title\": \"修剪视频\"\n  },\n  \"videoToGif\": {\n    \"description\": \"将视频文件转换为 GIF 动画格式。提取特定时间范围并创建可共享的动画图像。\",\n    \"shortDescription\": \"将视频转换为动画 GIF\",\n    \"title\": \"视频转GIF\"\n  }\n}\n"
  },
  {
    "path": "public/locales/zh/xml.json",
    "content": "{\n  \"xmlBeautifier\": {\n    \"description\": \"使用适当的缩进和间距来格式化 XML。\",\n    \"indentation\": \"缩进\",\n    \"inputTitle\": \"输入 XML\",\n    \"resultTitle\": \"美化 XML\",\n    \"shortDescription\": \"格式化和美化 XML 代码\",\n    \"title\": \"XML美化器\",\n    \"toolInfo\": {\n      \"description\": \"此工具允许您使用适当的缩进和间距来格式化 XML 数据，使其更易读且更易于使用。\",\n      \"title\": \"XML美化器\"\n    },\n    \"useSpaces\": \"使用空格\",\n    \"useSpacesDescription\": \"使用空格缩进输出\",\n    \"useTabs\": \"使用标签\",\n    \"useTabsDescription\": \"使用制表符缩进输出。\"\n  },\n  \"xmlValidator\": {\n    \"description\": \"验证 XML 语法和结构。\",\n    \"placeholder\": \"在此处粘贴或导入 XML...\",\n    \"shortDescription\": \"验证 XML 代码是否存在错误\",\n    \"title\": \"XML验证器\",\n    \"toolInfo\": {\n      \"description\": \"此工具可用于验证 XML 的语法和结构。它会检查 XML 格式是否正确，并针对发现的任何问题提供详细的错误消息。\",\n      \"title\": \"XML验证器\"\n    }\n  },\n  \"xmlViewer\": {\n    \"description\": \"以树状格式查看和探索 XML 结构。\",\n    \"inputTitle\": \"输入 XML\",\n    \"resultTitle\": \"XML 树视图\",\n    \"title\": \"XML 查看器\",\n    \"toolInfo\": {\n      \"description\": \"该工具允许您以分层树格式查看 XML 数据，从而更容易探索和理解 XML 文档的结构。\",\n      \"title\": \"XML 查看器\"\n    }\n  }\n}\n"
  },
  {
    "path": "public/robots.txt",
    "content": "\nUser-Agent: *\nAllow: /\n"
  },
  {
    "path": "public/site.webmanifest",
    "content": "{\n  \"name\": \"OmniTools\",\n  \"short_name\": \"OmniTools\",\n  \"icons\": [\n    {\n      \"src\": \"/web-app-manifest-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable\"\n    },\n    {\n      \"src\": \"/web-app-manifest-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable\"\n    }\n  ],\n  \"theme_color\": \"#ffffff\",\n  \"background_color\": \"#ffffff\",\n  \"display\": \"standalone\"\n}"
  },
  {
    "path": "scripts/add-i18n-to-meta.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n\nconst TYPE_MAPPING = { 'image-generic': 'image', png: 'image' };\n// Helper function to convert kebab-case to camelCase\nfunction toCamelCase(str) {\n  return str.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());\n}\n\n// Helper function to parse meta.ts file and extract required fields\nfunction parseMeta(filePath) {\n  const content = fs.readFileSync(filePath, 'utf8');\n\n  // Extract category from defineTool first parameter\n  const categoryMatch = content.match(/defineTool\\s*\\(\\s*['\"]([^'\"]+)['\"]/);\n  if (!categoryMatch) {\n    throw new Error(`Could not find category in ${filePath}`);\n  }\n  const category = categoryMatch[1];\n\n  // Extract name, description, shortDescription, and longDescription\n  const nameMatch = content.match(/name\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n  const descMatch = content.match(/description\\s*:\\s*['\"`]([\\s\\S]*?)['\"`]/);\n  const shortMatch = content.match(\n    /shortDescription\\s*:\\s*['\"`]([^'\"`]+)['\"`]/\n  );\n  const longMatch = content.match(/longDescription\\s*:\\s*['\"`]([\\s\\S]*?)['\"`]/);\n\n  if (!nameMatch || !descMatch || !shortMatch) {\n    console.warn(`⚠️  Missing required fields in ${filePath}`);\n    console.warn(\n      `   name: ${!!nameMatch}, description: ${!!descMatch}, shortDescription: ${!!shortMatch}`\n    );\n    return null;\n  }\n\n  return {\n    category,\n    name: nameMatch[1],\n    description: descMatch[1].replace(/\\s+/g, ' ').trim(),\n    shortDescription: shortMatch[1],\n    longDescription: longMatch ? longMatch[1].replace(/\\s+/g, ' ').trim() : null\n  };\n}\n\n// Helper function to check if meta.ts already has i18n field\nfunction hasI18nField(filePath) {\n  const content = fs.readFileSync(filePath, 'utf8');\n  return content.includes('i18n:');\n}\n\n// Helper function to add i18n field to meta.ts\nfunction addI18nToMeta(filePath, category, toolName, hasLongDescription) {\n  const content = fs.readFileSync(filePath, 'utf8');\n\n  // Build i18n object\n  let i18nObject = `  i18n: {\n    name: '${category}:${toolName}.title',\n    description: '${category}:${toolName}.description',\n    shortDescription: '${category}:${toolName}.shortDescription'`;\n\n  if (hasLongDescription) {\n    i18nObject += `,\n    longDescription: '${category}:${toolName}.longDescription'`;\n  }\n\n  i18nObject += `\n  },`;\n\n  // Find the position to insert i18n (after the export line but before the closing bracket)\n  const exportMatch = content.match(/export const tool = defineTool\\([^{]*\\{/);\n  if (!exportMatch) {\n    throw new Error(`Could not find export structure in ${filePath}`);\n  }\n\n  const insertPosition = exportMatch.index + exportMatch[0].length;\n  const beforeInsert = content.substring(0, insertPosition);\n  const afterInsert = content.substring(insertPosition);\n\n  // Insert i18n field at the beginning of the object\n  const updatedContent = beforeInsert + '\\n' + i18nObject + afterInsert;\n\n  fs.writeFileSync(filePath, updatedContent, 'utf8');\n}\n\n// Helper function to get category i18n file path\nfunction getCategoryI18nPath(category) {\n  const PROJECT_ROOT = path.resolve(__dirname, '..');\n  // Special case: image-generic tools use the image folder for i18n\n  const folderName = TYPE_MAPPING[category] || category;\n  return path.join(\n    PROJECT_ROOT,\n    'src',\n    'pages',\n    'tools',\n    folderName,\n    'i18n',\n    'en.json'\n  );\n}\n\n// Helper function to load category i18n data\nfunction loadCategoryI18n(category) {\n  const i18nPath = getCategoryI18nPath(category);\n\n  try {\n    if (fs.existsSync(i18nPath)) {\n      const i18nRaw = fs.readFileSync(i18nPath, 'utf8');\n      return JSON.parse(i18nRaw);\n    } else {\n      const i18nDir = path.dirname(i18nPath);\n      if (!fs.existsSync(i18nDir)) {\n        fs.mkdirSync(i18nDir, { recursive: true });\n      }\n      return {};\n    }\n  } catch (err) {\n    console.error(`❌ Failed to parse ${i18nPath}:`, err.message);\n    return {};\n  }\n}\n\n// Helper function to save category i18n data\nfunction saveCategoryI18n(category, data) {\n  const i18nPath = getCategoryI18nPath(category);\n  fs.writeFileSync(i18nPath, JSON.stringify(data, null, 2) + '\\n', 'utf8');\n  return i18nPath;\n}\n\n// Main execution\nconsole.log('🚀 Adding i18n fields to meta.ts files...\\n');\n\nconst PROJECT_ROOT = path.resolve(__dirname, '..');\n\n// Target files as specified\nconst rootDir = path.join(PROJECT_ROOT, 'src/pages/tools');\nconst metaFiles = [];\n\nfunction findMetaFiles(dir) {\n  const items = fs.readdirSync(dir, { withFileTypes: true });\n\n  for (const item of items) {\n    const fullPath = path.join(dir, item.name);\n\n    if (item.isDirectory()) {\n      findMetaFiles(fullPath); // Recurse\n    } else if (item.isFile() && item.name === 'meta.ts') {\n      metaFiles.push(fullPath);\n    }\n  }\n}\n\nfindMetaFiles(rootDir);\n\nlet processedCount = 0;\nlet skippedCount = 0;\nlet errorCount = 0;\nconst updatedCategories = new Set();\nconst categoryData = {};\n\n// Process each target file\nmetaFiles.forEach((filePath) => {\n  try {\n    // Check if file exists\n    if (!fs.existsSync(filePath)) {\n      console.error(`❌ File not found: ${filePath}`);\n      errorCount++;\n      return;\n    }\n\n    // Check if i18n field already exists\n    if (hasI18nField(filePath)) {\n      console.log(`⏭️  Skipped ${filePath} (already has i18n field)`);\n      skippedCount++;\n      return;\n    }\n\n    // Parse meta.ts file\n    const parsed = parseMeta(filePath);\n    if (!parsed) {\n      errorCount++;\n      return;\n    }\n\n    const {\n      category: rawCategory,\n      name,\n      description,\n      shortDescription,\n      longDescription\n    } = parsed;\n\n    const category = TYPE_MAPPING[rawCategory] || rawCategory;\n    // Get tool name from folder path\n    const toolFolderName = path.basename(path.dirname(filePath));\n    const toolKey = toCamelCase(toolFolderName); // camelCase for i18n file keys\n\n    // Load category i18n data if not already loaded\n    if (!categoryData[category]) {\n      categoryData[category] = loadCategoryI18n(category);\n    }\n\n    // Ensure tool entry exists in i18n\n    if (!categoryData[category][toolKey]) {\n      categoryData[category][toolKey] = {};\n    }\n\n    const entry = categoryData[category][toolKey];\n    let hasI18nChanges = false;\n\n    // Add missing fields to i18n\n    if (!entry.title) {\n      entry.title = name;\n      hasI18nChanges = true;\n    }\n    if (!entry.description) {\n      entry.description = description;\n      hasI18nChanges = true;\n    }\n    if (!entry.shortDescription) {\n      entry.shortDescription = shortDescription;\n      hasI18nChanges = true;\n    }\n    if (longDescription && !entry.longDescription) {\n      entry.longDescription = longDescription;\n      hasI18nChanges = true;\n    }\n\n    // Add i18n field to meta.ts\n    addI18nToMeta(filePath, category, toolKey, !!longDescription);\n\n    if (hasI18nChanges) {\n      updatedCategories.add(category);\n    }\n\n    console.log(`✅ Added i18n to ${filePath}`);\n    processedCount++;\n  } catch (err) {\n    console.error(`❌ Error processing ${filePath}:`, err.message);\n    errorCount++;\n  }\n});\n\n// Save updated category i18n files\nif (updatedCategories.size > 0) {\n  console.log('\\n💾 Saving updated i18n files...');\n  for (const category of updatedCategories) {\n    const savedPath = saveCategoryI18n(category, categoryData[category]);\n    console.log(`   📁 ${path.relative(PROJECT_ROOT, savedPath)}`);\n  }\n}\n\n// Summary\nconsole.log('\\n📊 Summary:');\nconsole.log(`   ✅ Processed: ${processedCount} files`);\nconsole.log(`   ⏭️  Skipped: ${skippedCount} files (already had i18n)`);\nconsole.log(`   ❌ Errors: ${errorCount} files`);\nconsole.log(\n  `\\n🎉 Successfully updated ${processedCount} meta.ts files and ${updatedCategories.size} i18n files!`\n);\n"
  },
  {
    "path": "scripts/cleanup-empty-directories.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n\n/**\n * Recursively delete all empty folders in a directory\n * @param {string} dirPath - The directory path to process\n * @param {boolean} deleteRoot - Whether to delete the root directory if it becomes empty\n * @returns {boolean} - Returns true if the directory is empty after processing\n */\nfunction deleteEmptyFolders(dirPath, deleteRoot = false) {\n  if (!fs.existsSync(dirPath)) {\n    console.log(`Directory does not exist: ${dirPath}`);\n    return false;\n  }\n\n  if (!fs.statSync(dirPath).isDirectory()) {\n    console.log(`Path is not a directory: ${dirPath}`);\n    return false;\n  }\n\n  let files;\n  try {\n    files = fs.readdirSync(dirPath);\n  } catch (err) {\n    console.error(`Error reading directory ${dirPath}:`, err.message);\n    return false;\n  }\n\n  // Process each item in the directory\n  for (const file of files) {\n    const fullPath = path.join(dirPath, file);\n\n    try {\n      const stat = fs.statSync(fullPath);\n\n      if (stat.isDirectory()) {\n        // Recursively process subdirectories\n        const isEmpty = deleteEmptyFolders(fullPath, true);\n\n        // If subdirectory is empty, remove it from the files array\n        if (isEmpty) {\n          const index = files.indexOf(file);\n          if (index > -1) {\n            files.splice(index, 1);\n          }\n        }\n      }\n    } catch (err) {\n      console.error(`Error processing ${fullPath}:`, err.message);\n    }\n  }\n\n  // Check if directory is empty after processing\n  const isEmpty = files.length === 0;\n\n  if (isEmpty && deleteRoot) {\n    try {\n      fs.rmdirSync(dirPath);\n      console.log(`Deleted empty directory: ${dirPath}`);\n      return true;\n    } catch (err) {\n      console.error(`Error deleting directory ${dirPath}:`, err.message);\n      return false;\n    }\n  }\n\n  return isEmpty;\n}\n\n/**\n * Main function to start the cleanup process\n * @param {string} targetPath - The root directory to clean up\n */\nfunction cleanupEmptyFolders(targetPath) {\n  console.log(`Starting cleanup of empty folders in: ${targetPath}`);\n\n  const absolutePath = path.resolve(targetPath);\n  const result = deleteEmptyFolders(absolutePath, false);\n\n  if (result) {\n    console.log(\n      'Cleanup completed. Root directory is empty but was not deleted.'\n    );\n  } else {\n    console.log('Cleanup completed.');\n  }\n}\n\n// Usage example\nconst targetDirectory = process.argv[2] || './target-folder';\ncleanupEmptyFolders(targetDirectory);\n\n// Export for use as a module\nmodule.exports = { deleteEmptyFolders, cleanupEmptyFolders };\n"
  },
  {
    "path": "scripts/create-tool.mjs",
    "content": "import { readFile, writeFile } from 'fs/promises';\nimport fs from 'fs';\nimport { dirname, join, sep } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst currentDirname = dirname(fileURLToPath(import.meta.url));\n\nconst toolName = process.argv[2];\nconst folder = process.argv[3];\n\nconst toolsDir = join(\n  currentDirname,\n  '..',\n  'src',\n  'pages',\n  'tools',\n  folder ?? ''\n);\nif (!toolName) {\n  throw new Error('Please specify a toolname.');\n}\n\nfunction capitalizeFirstLetter(string) {\n  return string.charAt(0).toUpperCase() + string.slice(1);\n}\n\nfunction createFolderStructure(basePath, foldersToCreateIndexCount) {\n  const folderArray = basePath.split(sep);\n\n  function recursiveCreate(currentBase, index) {\n    if (index >= folderArray.length) {\n      return;\n    }\n    const currentPath = join(currentBase, folderArray[index]);\n    if (!fs.existsSync(currentPath)) {\n      fs.mkdirSync(currentPath, { recursive: true });\n    }\n    const indexPath = join(currentPath, 'index.ts');\n    if (\n      !fs.existsSync(indexPath) &&\n      index < folderArray.length - 1 &&\n      index >= folderArray.length - 1 - foldersToCreateIndexCount\n    ) {\n      fs.writeFileSync(\n        indexPath,\n        `export const ${\n          currentPath.split(sep)[currentPath.split(sep).length - 1]\n        }Tools = [];\\n`\n      );\n      console.log(`File created: ${indexPath}`);\n    }\n    // Recursively create the next folder\n    recursiveCreate(currentPath, index + 1);\n  }\n\n  // Start the recursive folder creation\n  recursiveCreate('.', 0);\n}\n\nconst toolNameCamelCase = toolName.replace(/-./g, (x) => x[1].toUpperCase());\nconst toolNameTitleCase =\n  toolName[0].toUpperCase() + toolName.slice(1).replace(/-/g, ' ');\nconst toolDir = join(toolsDir, toolName);\nconst type = folder.split(sep)[folder.split(sep).length - 1];\nawait createFolderStructure(toolDir, folder.split(sep).length);\nconsole.log(`Directory created: ${toolDir}`);\n\nconst createToolFile = async (name, content) => {\n  const filePath = join(toolDir, name);\n  await writeFile(filePath, content.trim());\n  console.log(`File created: ${filePath}`);\n};\n\ncreateToolFile(\n  `index.tsx`,\n  `\nimport { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { main } from './service';\nimport { InitialValuesType } from './types';\n\nconst initialValues: InitialValuesType = {\n  // splitSeparator: '\\\\n'\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Split a String',\n    description: 'This example shows how to split a string into multiple lines',\n    sampleText: 'Hello World,Hello World',\n    sampleResult: \\`Hello World\nHello World\\`,\n    sampleOptions: {\n      //     splitSeparator: ','\n    }\n  }\n];\nexport default function ${capitalizeFirstLetter(toolNameCamelCase)}({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    setResult(main(input, values));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Example Settings',\n      component: <Box></Box>\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      exampleCards={exampleCards}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: \\`What is a \\${title}?\\`, description: longDescription }}\n    />\n  );\n}\n`\n);\nconst validNamespaces = [\n  'string',\n  'number',\n  'video',\n  'list',\n  'json',\n  'time',\n  'csv',\n  'pdf',\n  'audio',\n  'xml',\n  'translation',\n  'image'\n];\nconst isValidI18nNamespace = (value) => {\n  return validNamespaces.includes(value);\n};\n\nconst getI18nNamespaceFromToolCategory = (category) => {\n  // Map image-related categories to 'image'\n  if (['png', 'image-generic'].includes(category)) {\n    return 'image';\n  } else if (['gif'].includes(category)) {\n    return 'video';\n  }\n  // Use type guard to check if category is a valid I18nNamespaces\n  if (isValidI18nNamespace(category)) {\n    return category;\n  }\n\n  return 'translation';\n};\n\nconst i18nNamespace = getI18nNamespaceFromToolCategory(type);\ncreateToolFile(\n  `meta.ts`,\n  `\nimport { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('${type}', {\n  i18n: {\n    name: '${i18nNamespace}:${toolNameCamelCase}.title',\n    description: '${i18nNamespace}:${toolNameCamelCase}.description',\n    shortDescription: '${i18nNamespace}:${toolNameCamelCase}.shortDescription',\n    longDescription: '${i18nNamespace}:${toolNameCamelCase}.longDescription'\n  },\n  path: '${toolName}',\n  icon: '',\n  keywords: ['${toolName.split('-').join(\"', '\")}'],\n  component: lazy(() => import('./index'))\n});\n`\n);\n\ncreateToolFile(\n  `service.ts`,\n  `\nimport { InitialValuesType } from './types';\n\nexport function main(input: string, options: InitialValuesType): string {\n  return input;\n}\n`\n);\ncreateToolFile(\n  `types.ts`,\n  `\nexport type InitialValuesType = {\n  // splitSeparator: string;\n};\n`\n);\ncreateToolFile(\n  `${toolName}.service.test.ts`,\n  `\nimport { expect, describe, it } from 'vitest';\n// import { main } from './service';\n//\n// describe('${toolName}', () => {\n//\n// })\n`\n);\n\n// createToolFile(\n//   `${toolName}.e2e.spec.ts`,\n//   `\n// import { test, expect } from '@playwright/test';\n//\n// test.describe('Tool - ${toolNameTitleCase}', () => {\n//   test.beforeEach(async ({ page }) => {\n//     await page.goto('/${toolName}');\n//   });\n//\n//   test('Has correct title', async ({ page }) => {\n//     await expect(page).toHaveTitle('${toolNameTitleCase} - IT Tools');\n//   });\n//\n//   test('', async ({ page }) => {\n//\n//   });\n// });\n//\n// `\n// )\n\nconst toolsIndex = join(toolsDir, 'index.ts');\nconst indexContent = await readFile(toolsIndex, { encoding: 'utf-8' }).then(\n  (r) => r.split('\\n')\n);\n\nindexContent.splice(\n  0,\n  0,\n  `import { tool as ${type}${capitalizeFirstLetter(\n    toolNameCamelCase\n  )} } from './${toolName}/meta';`\n);\nwriteFile(toolsIndex, indexContent.join('\\n'));\n\n// Update locale JSON file\nconst localeFilePath = join(\n  currentDirname,\n  '..',\n  'public',\n  'locales',\n  'en',\n  `${i18nNamespace}.json`\n);\n\nlet localeData = {};\nif (fs.existsSync(localeFilePath)) {\n  const localeContent = await readFile(localeFilePath, { encoding: 'utf-8' });\n  localeData = JSON.parse(localeContent);\n}\n\nlocaleData[toolNameCamelCase] = {\n  title: toolNameTitleCase,\n  description: '',\n  shortDescription: '',\n  longDescription: ''\n};\n\n// Write updated locale file\nawait writeFile(localeFilePath, JSON.stringify(localeData, null, 2));\nconsole.log(`Added import in: ${toolsIndex}`);\n"
  },
  {
    "path": "scripts/locize-upload.js",
    "content": "// one-time-upload.js\n// Simple script to upload your existing translations to Locize once\n\nconst fs = require('fs');\nconst https = require('https');\n\n// Configuration\nconst LOCIZE_PROJECT_ID = 'e7156a3e-66fb-4035-a0f0-cebf1c63a3ba';\nconst LOCIZE_API_KEY = process.env.LOCIZE_API_KEY; // Replace with your actual API key\nconst LOCIZE_VERSION = 'latest';\n\n// Define your translation files\nconst translationFiles = [\n  // English translations\n  { lang: 'en', namespace: 'translation', file: '../src/i18n/en.json' },\n  {\n    lang: 'en',\n    namespace: 'list',\n    file: '../src/pages/tools/list/i18n/en.json'\n  },\n  {\n    lang: 'en',\n    namespace: 'string',\n    file: '../src/pages/tools/string/i18n/en.json'\n  },\n  { lang: 'en', namespace: 'csv', file: '../src/pages/tools/csv/i18n/en.json' },\n  {\n    lang: 'en',\n    namespace: 'json',\n    file: '../src/pages/tools/json/i18n/en.json'\n  },\n  { lang: 'en', namespace: 'pdf', file: '../src/pages/tools/pdf/i18n/en.json' },\n  {\n    lang: 'en',\n    namespace: 'image',\n    file: '../src/pages/tools/image/i18n/en.json'\n  },\n  {\n    lang: 'en',\n    namespace: 'audio',\n    file: '../src/pages/tools/audio/i18n/en.json'\n  },\n  {\n    lang: 'en',\n    namespace: 'video',\n    file: '../src/pages/tools/video/i18n/en.json'\n  },\n  {\n    lang: 'en',\n    namespace: 'number',\n    file: '../src/pages/tools/number/i18n/en.json'\n  },\n  {\n    lang: 'en',\n    namespace: 'time',\n    file: '../src/pages/tools/time/i18n/en.json'\n  },\n  { lang: 'en', namespace: 'xml', file: '../src/pages/tools/xml/i18n/en.json' },\n\n  // Hindi translations\n  { lang: 'hi', namespace: 'translation', file: '../src/i18n/hi.json' },\n  {\n    lang: 'hi',\n    namespace: 'list',\n    file: '../src/pages/tools/list/i18n/hi.json'\n  },\n  {\n    lang: 'hi',\n    namespace: 'string',\n    file: '../src/pages/tools/string/i18n/hi.json'\n  },\n  { lang: 'hi', namespace: 'csv', file: '../src/pages/tools/csv/i18n/hi.json' },\n  {\n    lang: 'hi',\n    namespace: 'json',\n    file: '../src/pages/tools/json/i18n/hi.json'\n  },\n  { lang: 'hi', namespace: 'pdf', file: '../src/pages/tools/pdf/i18n/hi.json' },\n  {\n    lang: 'hi',\n    namespace: 'image',\n    file: '../src/pages/tools/image/i18n/hi.json'\n  },\n  {\n    lang: 'hi',\n    namespace: 'audio',\n    file: '../src/pages/tools/audio/i18n/hi.json'\n  },\n  {\n    lang: 'hi',\n    namespace: 'video',\n    file: '../src/pages/tools/video/i18n/hi.json'\n  },\n  {\n    lang: 'hi',\n    namespace: 'number',\n    file: '../src/pages/tools/number/i18n/hi.json'\n  },\n  {\n    lang: 'hi',\n    namespace: 'time',\n    file: '../src/pages/tools/time/i18n/hi.json'\n  },\n  { lang: 'hi', namespace: 'xml', file: '../src/pages/tools/xml/i18n/hi.json' }\n];\n\nfunction flattenJson(obj, prefix = '') {\n  const flattened = {};\n\n  for (const key in obj) {\n    if (obj.hasOwnProperty(key)) {\n      const newKey = prefix ? `${prefix}.${key}` : key;\n\n      if (\n        typeof obj[key] === 'object' &&\n        obj[key] !== null &&\n        !Array.isArray(obj[key])\n      ) {\n        // Recursively flatten nested objects\n        Object.assign(flattened, flattenJson(obj[key], newKey));\n      } else {\n        // It's a primitive value or array\n        flattened[newKey] = obj[key];\n      }\n    }\n  }\n\n  return flattened;\n}\n\nfunction uploadToLocize(lang, namespace, data) {\n  return new Promise((resolve, reject) => {\n    // Flatten the JSON structure for Locize API\n    const flattenedData = flattenJson(data);\n    const postData = JSON.stringify(flattenedData);\n\n    const options = {\n      hostname: 'api.locize.app',\n      port: 443,\n      path: `/update/${LOCIZE_PROJECT_ID}/${LOCIZE_VERSION}/${lang}/${namespace}`,\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${LOCIZE_API_KEY}`,\n        'Content-Type': 'application/json',\n        'Content-Length': Buffer.byteLength(postData)\n      }\n    };\n\n    const req = https.request(options, (res) => {\n      let data = '';\n      res.on('data', (chunk) => (data += chunk));\n      res.on('end', () => {\n        if (res.statusCode === 200) {\n          resolve(JSON.parse(data));\n        } else {\n          reject(new Error(`HTTP ${res.statusCode}: ${data}`));\n        }\n      });\n    });\n\n    req.on('error', reject);\n    req.write(postData);\n    req.end();\n  });\n}\n\nasync function main() {\n  console.log('Starting one-time upload to Locize...\\n');\n\n  let successCount = 0;\n  let errorCount = 0;\n\n  for (const { lang, namespace, file } of translationFiles) {\n    try {\n      // Check if file exists\n      if (!fs.existsSync(file)) {\n        console.log(`⚠️  File not found: ${file}`);\n        continue;\n      }\n\n      // Read translation file\n      const translations = JSON.parse(fs.readFileSync(file, 'utf8'));\n      const flattenedTranslations = flattenJson(translations);\n      const keyCount = Object.keys(flattenedTranslations).length;\n\n      if (keyCount === 0) {\n        console.log(`⚠️  Empty file: ${lang}/${namespace}`);\n        continue;\n      }\n\n      // Upload to Locize\n      await uploadToLocize(lang, namespace, translations);\n      console.log(`✅ ${lang}/${namespace} - ${keyCount} keys uploaded`);\n      successCount++;\n    } catch (error) {\n      console.error(`❌ ${lang}/${namespace} - Error: ${error.message}`);\n      errorCount++;\n    }\n  }\n\n  console.log('\\n=== Upload Summary ===');\n  console.log(`✅ Successful uploads: ${successCount}`);\n  console.log(`❌ Failed uploads: ${errorCount}`);\n  console.log(`📊 Total files processed: ${successCount + errorCount}`);\n\n  if (errorCount === 0) {\n    console.log('\\n🎉 All translations uploaded successfully!');\n    console.log('You can now view them in your Locize dashboard.');\n  }\n}\n\n// Run the upload\nmain().catch(console.error);\n"
  },
  {
    "path": "scripts/update-i18n-from-meta.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n\n// Helper function to convert kebab-case to camelCase\nfunction toCamelCase(str) {\n  return str.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());\n}\n\n// Helper function to recursively find all meta.ts files\nfunction findMetaFiles(dir) {\n  const files = [];\n\n  function traverse(currentDir) {\n    const items = fs.readdirSync(currentDir, { withFileTypes: true });\n\n    for (const item of items) {\n      const fullPath = path.join(currentDir, item.name);\n\n      if (item.isDirectory()) {\n        traverse(fullPath);\n      } else if (item.name === 'meta.ts') {\n        files.push(fullPath);\n      }\n    }\n  }\n\n  traverse(dir);\n  return files;\n}\n\n// Helper function to parse meta.ts file and extract the required fields\nfunction parseMeta(filePath) {\n  const content = fs.readFileSync(filePath, 'utf8');\n\n  // Extract category from defineTool first parameter\n  const categoryMatch = content.match(/defineTool\\s*\\(\\s*['\"]([^'\"]+)['\"]/);\n  if (!categoryMatch) {\n    throw new Error(`Could not find category in ${filePath}`);\n  }\n  const category = categoryMatch[1];\n\n  // Extract name, description, and shortDescription\n  const nameMatch = content.match(/name\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n  const descMatch = content.match(\n    /description\\s*:\\s*['\"`]([\\s\\S]*?)['\"`]\\s*,\\s*shortDescription/\n  );\n  const shortMatch = content.match(\n    /shortDescription\\s*:\\s*['\"`]([^'\"`]+)['\"`]/\n  );\n\n  if (!nameMatch || !descMatch || !shortMatch) {\n    console.warn(`⚠️  Missing fields in ${filePath}`);\n    console.warn(\n      `   name: ${!!nameMatch}, description: ${!!descMatch}, shortDescription: ${!!shortMatch}`\n    );\n    return null;\n  }\n\n  return {\n    category,\n    name: nameMatch[1],\n    description: descMatch[1].replace(/\\s+/g, ' ').trim(),\n    shortDescription: shortMatch[1]\n  };\n}\n\n// Main execution\nconsole.log('🚀 Starting i18n extraction from meta.ts files...\\n');\n\nconst PROJECT_ROOT = path.resolve(__dirname, '..');\n\n// Helper function to get or create category i18n file\nfunction getCategoryI18nPath(category) {\n  return path.join(\n    PROJECT_ROOT,\n    'src',\n    'pages',\n    'tools',\n    category,\n    'i18n',\n    'en.json'\n  );\n}\n\nfunction loadCategoryI18n(category) {\n  const i18nPath = getCategoryI18nPath(category);\n\n  try {\n    if (fs.existsSync(i18nPath)) {\n      const i18nRaw = fs.readFileSync(i18nPath, 'utf8');\n      return JSON.parse(i18nRaw);\n    } else {\n      // Create directory if it doesn't exist\n      const i18nDir = path.dirname(i18nPath);\n      if (!fs.existsSync(i18nDir)) {\n        fs.mkdirSync(i18nDir, { recursive: true });\n      }\n      return {};\n    }\n  } catch (err) {\n    console.error(`❌ Failed to parse ${i18nPath}:`, err.message);\n    return {};\n  }\n}\n\nfunction saveCategoryI18n(category, data) {\n  const i18nPath = getCategoryI18nPath(category);\n\n  // Create backup\n  // if (fs.existsSync(i18nPath)) {\n  //   fs.copyFileSync(i18nPath, i18nPath + '.bak');\n  // }\n\n  // Write updated file\n  fs.writeFileSync(i18nPath, JSON.stringify(data, null, 2) + '\\n', 'utf8');\n  return i18nPath;\n}\n\n// 2) Find all meta.ts files under src/pages/tools\nconst toolsDir = path.join(PROJECT_ROOT, 'src', 'pages', 'tools');\nconst files = findMetaFiles(toolsDir);\nconsole.log(`📁 Found ${files.length} meta.ts files\\n`);\n\nlet addedCount = 0;\nlet skippedCount = 0;\nlet errorCount = 0;\nconst updatedCategories = new Set();\n\n// 3) Process each meta.ts file\nconst categoryData = {};\n\nfiles.forEach((file) => {\n  try {\n    const relativePath = path.relative(PROJECT_ROOT, file);\n    const parsed = parseMeta(file);\n\n    if (!parsed) {\n      errorCount++;\n      return;\n    }\n\n    const { category, name, description, shortDescription } = parsed;\n\n    // Load category i18n data if not already loaded\n    if (!categoryData[category]) {\n      categoryData[category] = loadCategoryI18n(category);\n    }\n\n    // Get tool key from folder name (convert kebab-case to camelCase)\n    const toolSlug = path.basename(path.dirname(file));\n    const toolKey = toCamelCase(toolSlug);\n\n    // Ensure tool entry exists\n    if (!categoryData[category][toolKey]) {\n      categoryData[category][toolKey] = {};\n    }\n\n    const entry = categoryData[category][toolKey];\n    let hasChanges = false;\n\n    // Add missing fields\n    if (!entry.name) {\n      entry.name = name;\n      hasChanges = true;\n    }\n    if (!entry.description) {\n      entry.description = description;\n      hasChanges = true;\n    }\n    if (!entry.shortDescription) {\n      entry.shortDescription = shortDescription;\n      hasChanges = true;\n    }\n\n    if (hasChanges) {\n      console.log(`✅ Updated ${category}/${toolKey}`);\n      addedCount++;\n      updatedCategories.add(category);\n    } else {\n      console.log(`⏭️  Skipped ${category}/${toolKey} (already exists)`);\n      skippedCount++;\n    }\n  } catch (err) {\n    console.error(`❌ Error processing ${file}:`, err.message);\n    errorCount++;\n  }\n});\n\n// 4) Save updated category i18n files\nconsole.log('\\n💾 Saving updated i18n files...');\nfor (const category of updatedCategories) {\n  const savedPath = saveCategoryI18n(category, categoryData[category]);\n  console.log(`   📁 ${path.relative(PROJECT_ROOT, savedPath)}`);\n}\n\n// 6) Summary\nconsole.log('\\n📊 Summary:');\nconsole.log(`   ✅ Updated: ${addedCount} tools`);\nconsole.log(`   ⏭️  Skipped: ${skippedCount} tools (already had entries)`);\nconsole.log(`   ❌ Errors: ${errorCount} tools`);\nconsole.log(\n  `\\n🎉 Successfully updated ${updatedCategories.size} category i18n files!`\n);\n"
  },
  {
    "path": "src/@types/i18n.d.ts",
    "content": "import 'i18next';\n\nimport translation from '../../public/locales/en/translation.json';\nimport string from '../../public/locales/en/string.json';\nimport number from '../../public/locales/en/number.json';\nimport video from '../../public/locales/en/video.json';\nimport list from '../../public/locales/en/list.json';\nimport json from '../../public/locales/en/json.json';\nimport time from '../../public/locales/en/time.json';\nimport csv from '../../public/locales/en/csv.json';\nimport pdf from '../../public/locales/en/pdf.json';\nimport audio from '../../public/locales/en/audio.json';\nimport xml from '../../public/locales/en/xml.json';\nimport image from '../../public/locales/en/image.json';\nimport converters from '../../public/locales/en/converters.json';\n\ndeclare module 'i18next' {\n  interface CustomTypeOptions {\n    resources: {\n      translation: typeof translation;\n      string: typeof string;\n      number: typeof number;\n      video: typeof video;\n      list: typeof list;\n      json: typeof json;\n      time: typeof time;\n      csv: typeof csv;\n      pdf: typeof pdf;\n      audio: typeof audio;\n      xml: typeof xml;\n      image: typeof image;\n      converters: typeof converters;\n    };\n  }\n}\n"
  },
  {
    "path": "src/components/App.tsx",
    "content": "import { BrowserRouter, useRoutes } from 'react-router-dom';\nimport routesConfig from '../config/routesConfig';\nimport Navbar from './Navbar';\nimport { Suspense, useState, useEffect } from 'react';\nimport Loading from './Loading';\nimport { CssBaseline, Theme, ThemeProvider } from '@mui/material';\nimport { CustomSnackBarProvider } from '../contexts/CustomSnackBarContext';\nimport { SnackbarProvider } from 'notistack';\nimport { tools } from '../tools';\nimport './index.css';\nimport { darkTheme, lightTheme } from '../config/muiConfig';\nimport ScrollToTopButton from './ScrollToTopButton';\nimport { I18nextProvider } from 'react-i18next';\nimport i18n from '../i18n';\nimport { UserTypeFilterProvider } from 'providers/UserTypeFilterProvider';\n\nexport type Mode = 'dark' | 'light' | 'system';\n\nconst AppRoutes = () => {\n  const updatedRoutesConfig = [...routesConfig];\n  tools.forEach((tool) => {\n    updatedRoutesConfig.push({ path: tool.path, element: tool.component() });\n  });\n  return useRoutes(updatedRoutesConfig);\n};\n\nfunction App() {\n  const [mode, setMode] = useState<Mode>(\n    () => (localStorage.getItem('theme') || 'system') as Mode\n  );\n  const [theme, setTheme] = useState<Theme>(() => getTheme(mode));\n  useEffect(() => setTheme(getTheme(mode)), [mode]);\n\n  // Make sure to update the theme when the mode changes\n  useEffect(() => {\n    const systemDarkModeQuery = window.matchMedia(\n      '(prefers-color-scheme: dark)'\n    );\n    const handleThemeChange = (e: MediaQueryListEvent) => {\n      setTheme(e.matches ? darkTheme : lightTheme);\n    };\n    systemDarkModeQuery.addEventListener('change', handleThemeChange);\n\n    return () => {\n      systemDarkModeQuery.removeEventListener('change', handleThemeChange);\n    };\n  }, []);\n\n  return (\n    <I18nextProvider i18n={i18n}>\n      <ThemeProvider theme={theme}>\n        <CssBaseline />\n        <SnackbarProvider\n          maxSnack={5}\n          anchorOrigin={{\n            vertical: 'bottom',\n            horizontal: 'right'\n          }}\n        >\n          <CustomSnackBarProvider>\n            <UserTypeFilterProvider>\n              <BrowserRouter>\n                <Navbar\n                  mode={mode}\n                  onChangeMode={() => {\n                    setMode((prev) => nextMode(prev));\n                    localStorage.setItem('theme', nextMode(mode));\n                  }}\n                />\n                <Suspense fallback={<Loading />}>\n                  <AppRoutes />\n                </Suspense>\n              </BrowserRouter>\n            </UserTypeFilterProvider>\n          </CustomSnackBarProvider>\n        </SnackbarProvider>\n        <ScrollToTopButton />\n      </ThemeProvider>\n    </I18nextProvider>\n  );\n}\n\nfunction getTheme(mode: Mode): Theme {\n  switch (mode) {\n    case 'dark':\n      return darkTheme;\n    case 'light':\n      return lightTheme;\n    default:\n      return window.matchMedia('(prefers-color-scheme: dark)').matches\n        ? darkTheme\n        : lightTheme;\n  }\n}\n\nfunction nextMode(mode: Mode): Mode {\n  return mode === 'light' ? 'dark' : mode === 'dark' ? 'system' : 'light';\n}\n\nexport default App;\n"
  },
  {
    "path": "src/components/BackButton.tsx",
    "content": "import { IconButton } from '@mui/material';\nimport ArrowBackIcon from '@mui/icons-material/ArrowBack';\nimport { useNavigate, useNavigationType } from 'react-router-dom';\n\nconst BackButton = () => {\n  const navigate = useNavigate();\n  const navigationType = useNavigationType(); // Check if there is a history state\n  const disabled = navigationType === 'POP';\n  const handleBack = () => {\n    navigate(-1);\n  };\n\n  return (\n    <IconButton onClick={handleBack} disabled={disabled}>\n      <ArrowBackIcon color={disabled ? 'action' : 'primary'} />\n    </IconButton>\n  );\n};\n\nexport default BackButton;\n"
  },
  {
    "path": "src/components/Hero.tsx",
    "content": "import {\n  Autocomplete,\n  Box,\n  darken,\n  lighten,\n  Stack,\n  styled,\n  TextField,\n  useTheme\n} from '@mui/material';\nimport Typography from '@mui/material/Typography';\nimport SearchIcon from '@mui/icons-material/Search';\nimport Grid from '@mui/material/Grid';\nimport { useState } from 'react';\nimport { DefinedTool } from '@tools/defineTool';\nimport { filterTools, tools } from '@tools/index';\nimport { useNavigate } from 'react-router-dom';\nimport { Icon } from '@iconify/react';\nimport { getToolCategoryTitle } from '@utils/string';\nimport { useTranslation } from 'react-i18next';\nimport { FullI18nKey, validNamespaces } from '../i18n';\nimport {\n  getBookmarkedToolPaths,\n  isBookmarked,\n  toggleBookmarked\n} from '@utils/bookmark';\nimport IconButton from '@mui/material/IconButton';\nimport { useUserTypeFilter } from '../providers/UserTypeFilterProvider';\n\nconst GroupHeader = styled('div')(({ theme }) => ({\n  position: 'sticky',\n  top: '-8px',\n  padding: '4px 10px',\n  color: theme.palette.primary.main,\n  backgroundColor: lighten(theme.palette.primary.light, 0.85),\n  ...theme.applyStyles('dark', {\n    backgroundColor: darken(theme.palette.primary.main, 0.8)\n  })\n}));\n\nconst GroupItems = styled('ul')({\n  padding: 0\n});\n\ntype ToolInfo = {\n  label: FullI18nKey;\n  url: string;\n};\n\nexport default function Hero() {\n  const { t } = useTranslation(validNamespaces);\n  const [inputValue, setInputValue] = useState<string>('');\n  const theme = useTheme();\n  const { selectedUserTypes } = useUserTypeFilter();\n  const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(tools);\n  const [bookmarkedToolPaths, setBookmarkedToolPaths] = useState<string[]>(\n    getBookmarkedToolPaths()\n  );\n  const navigate = useNavigate();\n\n  const exampleTools: ToolInfo[] = [\n    {\n      label: 'translation:hero.examples.createTransparentImage',\n      url: '/image-generic/create-transparent'\n    },\n    {\n      label: 'translation:hero.examples.prettifyJson',\n      url: '/json/prettify'\n    },\n    {\n      label: 'translation:hero.examples.changeGifSpeed',\n      url: '/gif/change-speed'\n    },\n    {\n      label: 'translation:hero.examples.sortList',\n      url: '/list/sort'\n    },\n    {\n      label: 'translation:hero.examples.compressPng',\n      url: '/png/compress-png'\n    },\n    {\n      label: 'translation:hero.examples.splitText',\n      url: '/string/split'\n    },\n    {\n      label: 'translation:hero.examples.splitPdf',\n      url: '/pdf/split-pdf'\n    },\n    {\n      label: 'translation:hero.examples.trimVideo',\n      url: '/video/trim'\n    },\n    {\n      label: 'translation:hero.examples.calculateNumberSum',\n      url: '/number/sum'\n    }\n  ];\n\n  const handleInputChange = (\n    _event: React.ChangeEvent<{}>,\n    newInputValue: string\n  ) => {\n    setInputValue(newInputValue);\n    setFilteredTools(filterTools(tools, newInputValue, selectedUserTypes, t));\n  };\n\n  const toolsMap = new Map<string, ToolInfo>();\n  for (const tool of filteredTools) {\n    toolsMap.set(tool.path, {\n      label: tool.name,\n      url: '/' + tool.path\n    });\n  }\n\n  const displayedTools =\n    bookmarkedToolPaths.length > 0\n      ? bookmarkedToolPaths.flatMap((path) => {\n          const tool = toolsMap.get(path);\n          if (tool === undefined) {\n            return [];\n          }\n          return [tool];\n        })\n      : exampleTools;\n\n  return (\n    <Box width={{ xs: '90%', md: '80%', lg: '60%' }}>\n      <Stack mb={1} direction={'row'} spacing={1} justifyContent={'center'}>\n        <Typography sx={{ textAlign: 'center' }} fontSize={{ xs: 25, md: 30 }}>\n          {t('translation:hero.title')}{' '}\n          <Typography\n            fontSize={{ xs: 25, md: 30 }}\n            display={'inline'}\n            color={'primary'}\n          >\n            {t('translation:hero.brand')}\n          </Typography>\n        </Typography>\n      </Stack>\n      <Typography\n        sx={{ textAlign: 'center' }}\n        fontSize={{ xs: 15, md: 20 }}\n        mb={2}\n      >\n        {t('translation:hero.description')}\n      </Typography>\n\n      <Autocomplete\n        sx={{ mb: 2 }}\n        autoHighlight\n        options={filteredTools}\n        // Disable default MUI filtering since we already apply custom filterTools\n        filterOptions={(options) => options}\n        groupBy={(option) => option.type}\n        renderGroup={(params) => {\n          return (\n            <li key={params.key}>\n              <GroupHeader>{getToolCategoryTitle(params.group, t)}</GroupHeader>\n              <GroupItems>{params.children}</GroupItems>\n            </li>\n          );\n        }}\n        inputValue={inputValue}\n        getOptionLabel={(option) => t(option.name)}\n        renderInput={(params) => (\n          <TextField\n            {...params}\n            fullWidth\n            placeholder={t('translation:hero.searchPlaceholder')}\n            InputProps={{\n              ...params.InputProps,\n              endAdornment: <SearchIcon />,\n              sx: {\n                borderRadius: 4,\n                backgroundColor: 'background.paper'\n              }\n            }}\n            onChange={(event) => handleInputChange(event, event.target.value)}\n          />\n        )}\n        renderOption={(props, option) => (\n          <Box\n            component=\"li\"\n            {...props}\n            onClick={() => navigate('/' + option.path)}\n          >\n            <Stack\n              direction={'row'}\n              alignItems={'center'}\n              justifyContent={'space-between'}\n              width={'100%'}\n            >\n              <Stack direction={'row'} spacing={2} alignItems={'center'}>\n                <Icon fontSize={20} icon={option.icon} />\n                <Box>\n                  <Typography fontWeight={'bold'}>{t(option.name)}</Typography>\n                  <Typography fontSize={12}>\n                    {t(option.shortDescription)}\n                  </Typography>\n                </Box>\n              </Stack>\n              <IconButton\n                onClick={(e) => {\n                  e.stopPropagation();\n                  toggleBookmarked(option.path);\n                  setBookmarkedToolPaths(getBookmarkedToolPaths());\n                }}\n              >\n                <Icon\n                  fontSize={20}\n                  color={\n                    isBookmarked(option.path)\n                      ? theme.palette.primary.main\n                      : theme.palette.grey[500]\n                  }\n                  icon={\n                    isBookmarked(option.path)\n                      ? 'mdi:bookmark'\n                      : 'mdi:bookmark-plus-outline'\n                  }\n                />\n              </IconButton>\n            </Stack>\n          </Box>\n        )}\n        onChange={(event, newValue) => {\n          if (newValue) {\n            navigate('/' + newValue.path);\n          }\n        }}\n      />\n      <Grid container spacing={2} mt={2}>\n        {displayedTools.map((tool) => (\n          <Grid\n            onClick={() =>\n              navigate(tool.url.startsWith('/') ? tool.url : `/${tool.url}`)\n            }\n            item\n            xs={12}\n            md={6}\n            lg={4}\n            key={tool.label}\n          >\n            <Box\n              sx={{\n                display: 'flex',\n                justifyContent: 'center',\n                alignItems: 'center',\n                borderWidth: 1,\n                padding: 1,\n                borderRadius: 3,\n                borderColor: theme.palette.mode === 'dark' ? '#363b41' : 'grey',\n                borderStyle: 'solid',\n                backgroundColor: 'background.paper',\n                cursor: 'pointer',\n                '&:hover': {\n                  backgroundColor: 'background.hover'\n                },\n                height: '100%'\n              }}\n            >\n              <Stack direction={'row'} spacing={1} alignItems={'center'}>\n                <Typography textAlign={'center'}>{t(tool.label)}</Typography>\n                {bookmarkedToolPaths.length > 0 && (\n                  <IconButton\n                    onClick={(e) => {\n                      e.stopPropagation();\n                      const path = tool.url.substring(1);\n                      toggleBookmarked(path);\n                      setBookmarkedToolPaths(getBookmarkedToolPaths());\n                    }}\n                    size={'small'}\n                  >\n                    <Icon\n                      icon={'mdi:close'}\n                      color={theme.palette.grey[500]}\n                      fontSize={15}\n                    />\n                  </IconButton>\n                )}\n              </Stack>\n            </Box>\n          </Grid>\n        ))}\n      </Grid>\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/InputHeader.tsx",
    "content": "import Typography from '@mui/material/Typography';\nimport React from 'react';\n\nexport default function InputHeader({ title }: { title: string }) {\n  return (\n    <Typography mb={1} fontSize={30} color={'primary'}>\n      {title}\n    </Typography>\n  );\n}\n"
  },
  {
    "path": "src/components/Loading.css",
    "content": "#spinner {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  margin-top: 40px;\n  width: 56px;\n}\n\n#spinner > div {\n  width: 12px;\n  height: 12px;\n  background-color: #1e96f7;\n  border-radius: 100%;\n  display: inline-block;\n  -webkit-animation: fuse-bouncedelay 1s infinite ease-in-out both;\n  animation: fuse-bouncedelay 1s infinite ease-in-out both;\n}\n\n#spinner .bounce1 {\n  -webkit-animation-delay: -0.32s;\n  animation-delay: -0.32s;\n}\n\n#spinner .bounce2 {\n  -webkit-animation-delay: -0.16s;\n  animation-delay: -0.16s;\n}\n\n@-webkit-keyframes fuse-bouncedelay {\n  0%,\n  80%,\n  100% {\n    -webkit-transform: scale(0);\n  }\n  40% {\n    -webkit-transform: scale(1);\n  }\n}\n\n@keyframes fuse-bouncedelay {\n  0%,\n  80%,\n  100% {\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n  40% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n"
  },
  {
    "path": "src/components/Loading.tsx",
    "content": "import Typography from '@mui/material/Typography';\nimport Box from '@mui/material/Box';\nimport './Loading.css';\n\nfunction Loading() {\n  return (\n    <Box\n      sx={{\n        width: '100%',\n        height: 0.8 * window.innerHeight,\n        display: 'flex',\n        flexDirection: 'column',\n        justifyContent: 'center',\n        alignItems: 'center'\n      }}\n    >\n      <Typography color=\"primary\">Loading</Typography>\n      <Box\n        id=\"spinner\"\n        sx={{\n          '& > div': {\n            backgroundColor: 'palette.secondary.main'\n          }\n        }}\n      >\n        <div className=\"bounce1\" />\n        <div className=\"bounce2\" />\n        <div className=\"bounce3\" />\n      </Box>\n    </Box>\n  );\n}\n\nexport default Loading;\n"
  },
  {
    "path": "src/components/Navbar/index.tsx",
    "content": "import React, { ReactNode, useState } from 'react';\nimport AppBar from '@mui/material/AppBar';\nimport Toolbar from '@mui/material/Toolbar';\nimport Button from '@mui/material/Button';\nimport IconButton from '@mui/material/IconButton';\nimport MenuIcon from '@mui/icons-material/Menu';\nimport { Link, useNavigate } from 'react-router-dom';\nimport logo from 'assets/logo.png';\nimport logoWhite from 'assets/logo-white.png';\nimport {\n  Drawer,\n  List,\n  ListItem,\n  ListItemButton,\n  ListItemText,\n  Stack,\n  Select,\n  MenuItem,\n  FormControl\n} from '@mui/material';\nimport useMediaQuery from '@mui/material/useMediaQuery';\nimport { useTheme } from '@mui/material/styles';\nimport { Icon } from '@iconify/react';\nimport { Mode } from 'components/App';\nimport { useTranslation } from 'react-i18next';\n\ninterface NavbarProps {\n  mode: Mode;\n  onChangeMode: () => void;\n}\nconst languages = [\n  { code: 'en', label: 'English' },\n  { code: 'de', label: 'Deutsch' },\n  { code: 'es', label: 'Español' },\n  { code: 'fr', label: 'Français' },\n  { code: 'pt', label: 'Português' },\n  { code: 'ja', label: '日本語' },\n  { code: 'hi', label: 'हिंदी' },\n  { code: 'nl', label: 'Nederlands' },\n  { code: 'ru', label: 'Русский' },\n  { code: 'zh', label: '中文' }\n];\n\nconst Navbar: React.FC<NavbarProps> = ({\n  mode,\n  onChangeMode: onChangeMode\n}) => {\n  const { t, i18n } = useTranslation();\n  const navigate = useNavigate();\n  const theme = useTheme();\n  const isMobile = useMediaQuery(theme.breakpoints.down('md'));\n  const [drawerOpen, setDrawerOpen] = useState(false);\n  const toggleDrawer = (open: boolean) => () => {\n    setDrawerOpen(open);\n  };\n\n  const handleLanguageChange = (event: any) => {\n    const newLanguage = event.target.value;\n    i18n.changeLanguage(newLanguage);\n    localStorage.setItem('lang', newLanguage);\n  };\n\n  const navItems: { label: string; path: string }[] = [\n    // { label: 'Features', path: '/features' }\n    // { label: 'About Us', path: '/about-us' }\n  ];\n\n  const languageSelector = (\n    <FormControl size=\"small\" sx={{ minWidth: 120 }}>\n      <Select\n        value={i18n.language}\n        onChange={handleLanguageChange}\n        displayEmpty\n        sx={{\n          color: 'inherit',\n          '& .MuiSelect-icon': {\n            color: 'inherit'\n          },\n          '& .MuiOutlinedInput-notchedOutline': {\n            borderColor: 'transparent'\n          },\n          '&:hover .MuiOutlinedInput-notchedOutline': {\n            borderColor: 'transparent'\n          },\n          '&.Mui-focused .MuiOutlinedInput-notchedOutline': {\n            borderColor: 'transparent'\n          }\n        }}\n      >\n        {languages.map((lang) => (\n          <MenuItem key={lang.code} value={lang.code}>\n            {lang.label}\n          </MenuItem>\n        ))}\n      </Select>\n    </FormControl>\n  );\n\n  const buttons: ReactNode[] = [\n    languageSelector,\n    <Icon\n      key={mode}\n      onClick={onChangeMode}\n      style={{ cursor: 'pointer' }}\n      fontSize={30}\n      icon={\n        mode === 'dark'\n          ? 'ic:round-dark-mode'\n          : mode === 'light'\n            ? 'ic:round-light-mode'\n            : 'ic:round-contrast'\n      }\n    />,\n    <Icon\n      onClick={() => window.open('https://discord.gg/SDbbn3hT4b', '_blank')}\n      style={{ cursor: 'pointer' }}\n      fontSize={30}\n      icon={'ic:baseline-discord'}\n    />,\n    <iframe\n      src=\"https://ghbtns.com/github-btn.html?user=iib0011&repo=omni-tools&type=star&count=true&size=large\"\n      frameBorder=\"0\"\n      scrolling=\"0\"\n      width=\"150\"\n      height=\"30\"\n      title=\"GitHub\"\n    ></iframe>,\n    <Button\n      onClick={() => {\n        window.open(\n          'https://drive.google.com/file/d/1-r9-rDYnDJic9dnDywKTAsueehIAVp5F/view?usp=sharing',\n          '_blank'\n        );\n      }}\n      sx={{ borderRadius: '100px' }}\n      variant={'contained'}\n      startIcon={\n        <Icon\n          style={{ cursor: 'pointer' }}\n          fontSize={25}\n          icon={'hugeicons:job-search'}\n        />\n      }\n    >\n      {t('navbar.hireMe')}\n    </Button>\n  ];\n  const drawerList = (\n    <List>\n      {navItems.map((navItem) => (\n        <ListItemButton\n          key={navItem.path}\n          onClick={() => navigate(navItem.path)}\n        >\n          <ListItemText primary={navItem.label} />\n        </ListItemButton>\n      ))}\n      {buttons.map((button) => (\n        <ListItem>{button}</ListItem>\n      ))}\n    </List>\n  );\n\n  return (\n    <AppBar\n      position=\"static\"\n      sx={{\n        background: 'transparent',\n        boxShadow: 'none',\n        color: 'text.primary',\n        pt: 2\n      }}\n    >\n      <Toolbar\n        sx={{\n          justifyContent: 'space-between',\n          alignItems: 'center',\n          mx: { md: '50px', lg: '150px' }\n        }}\n      >\n        <Link to=\"/\">\n          <img\n            src={theme.palette.mode === 'light' ? logo : logoWhite}\n            width={isMobile ? '120px' : '200px'}\n          />\n        </Link>\n        {isMobile ? (\n          <>\n            <IconButton\n              color=\"inherit\"\n              onClick={toggleDrawer(true)}\n              sx={{\n                '&:hover': {\n                  backgroundColor: theme.palette.primary.main\n                }\n              }}\n            >\n              <MenuIcon />\n            </IconButton>\n            <Drawer\n              anchor=\"right\"\n              open={drawerOpen}\n              onClose={toggleDrawer(false)}\n            >\n              {drawerList}\n            </Drawer>\n          </>\n        ) : (\n          <Stack direction={'row'} spacing={3} alignItems={'center'}>\n            {navItems.map((item) => (\n              <Button\n                key={item.label}\n                color=\"inherit\"\n                sx={{\n                  '&:hover': {\n                    color: theme.palette.primary.main,\n                    transition: 'color 0.3s ease',\n                    backgroundColor: 'white'\n                  }\n                }}\n              >\n                <Link\n                  to={item.path}\n                  style={{ textDecoration: 'none', color: 'inherit' }}\n                >\n                  {item.label}\n                </Link>\n              </Button>\n            ))}\n            {buttons}\n          </Stack>\n        )}\n      </Toolbar>\n    </AppBar>\n  );\n};\n\nexport default Navbar;\n"
  },
  {
    "path": "src/components/ScrollToTopButton.tsx",
    "content": "import { useState, useEffect } from 'react';\nimport Button from '@mui/material/Button';\nimport { Icon } from '@iconify/react';\n\nexport default function ScrollToTopButton() {\n  const [visible, setVisible] = useState(false);\n\n  useEffect(() => {\n    const toggleVisibility = () => {\n      setVisible(window.scrollY > 100);\n    };\n\n    window.addEventListener('scroll', toggleVisibility);\n    return () => window.removeEventListener('scroll', toggleVisibility);\n  }, []);\n\n  const scrollToTop = () => {\n    window.scrollTo({ top: 0, behavior: 'smooth' });\n  };\n\n  if (!visible) return null;\n\n  return (\n    <Button\n      onClick={scrollToTop}\n      variant=\"contained\"\n      color=\"primary\"\n      sx={{\n        position: 'fixed',\n        bottom: 20,\n        right: 20,\n        zIndex: 9999,\n        minWidth: '40px',\n        width: '40px',\n        height: '40px',\n        borderRadius: '50%',\n        padding: 0,\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n      aria-label=\"Scroll to top\"\n    >\n      <Icon icon=\"mdi:arrow-up\" fontSize={24} style={{ color: 'white' }} />\n    </Button>\n  );\n}\n"
  },
  {
    "path": "src/components/Separator.tsx",
    "content": "import { Divider } from '@mui/material';\nimport React from 'react';\n\ntype SeparatorProps = {\n  backgroundColor: string;\n  margin: string;\n};\n\nexport default function Separator({ backgroundColor, margin }: SeparatorProps) {\n  return (\n    <Divider\n      orientation=\"horizontal\"\n      variant=\"fullWidth\"\n      className=\"my-4\"\n      sx={{\n        backgroundColor: backgroundColor,\n        height: '2px',\n        marginTop: margin,\n        marginBottom: margin\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/components/ToolBreadcrumb.tsx",
    "content": "import React from 'react';\nimport { Breadcrumbs, Typography, useTheme } from '@mui/material';\nimport { Link } from 'react-router-dom';\n\ninterface BreadcrumbItem {\n  title: string;\n  link?: string; // link is optional for the last item\n}\n\ninterface BreadcrumbComponentProps {\n  items: BreadcrumbItem[];\n}\n\nconst ToolBreadcrumb: React.FC<BreadcrumbComponentProps> = ({ items }) => {\n  const theme = useTheme();\n  return (\n    <Breadcrumbs>\n      {items.map((item, index) => {\n        if (index === items.length - 1 || !item.link) {\n          return (\n            <Typography color=\"textPrimary\" key={index}>\n              {item.title}\n            </Typography>\n          );\n        }\n        return (\n          <Link color={theme.palette.primary.main} to={item.link} key={index}>\n            {item.title}\n          </Link>\n        );\n      })}\n    </Breadcrumbs>\n  );\n};\n\nexport default ToolBreadcrumb;\n"
  },
  {
    "path": "src/components/ToolContent.tsx",
    "content": "import React, { ReactNode, useContext, useEffect } from 'react';\nimport { Box } from '@mui/material';\nimport { Formik, FormikValues, useFormikContext } from 'formik';\nimport ToolOptions, { GetGroupsType } from '@components/options/ToolOptions';\nimport ToolInputAndResult from '@components/ToolInputAndResult';\nimport ToolInfo from '@components/ToolInfo';\nimport Separator from '@components/Separator';\nimport ToolExamples, {\n  CardExampleType\n} from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { CustomSnackBarContext } from '../contexts/CustomSnackBarContext';\n\nconst FormikListenerComponent = <T,>({\n  input,\n  compute,\n  onValuesChange\n}: {\n  input: any;\n  compute: (optionsValues: T, input: any) => void;\n  onValuesChange?: (values: T) => void;\n}) => {\n  const { values } = useFormikContext<T>();\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n\n  React.useEffect(() => {\n    try {\n      compute(values, input);\n    } catch (exception: unknown) {\n      if (exception instanceof Error) showSnackBar(exception.message, 'error');\n      else console.error(exception);\n    }\n  }, [values, input, showSnackBar]);\n\n  useEffect(() => {\n    onValuesChange?.(values);\n  }, [onValuesChange, values]);\n  return null; // This component doesn't render anything\n};\n\ninterface ToolContentProps<Options, Input> extends ToolComponentProps {\n  inputComponent?: ReactNode;\n  resultComponent?: ReactNode;\n  renderCustomInput?: (\n    values: Options,\n    setFieldValue: (fieldName: string, value: any) => void\n  ) => ReactNode;\n  initialValues: Options;\n  /**\n   * should return non-empty array or null\n   */\n  getGroups: GetGroupsType<Options> | null;\n  compute: (optionsValues: Options, input: Input) => void;\n  toolInfo?: {\n    title: string;\n    description?: string;\n  };\n  input?: Input;\n  exampleCards?: CardExampleType<Options>[];\n  setInput?: React.Dispatch<React.SetStateAction<Input>>;\n  validationSchema?: any;\n  onValuesChange?: (values: Options) => void;\n  verticalGroups?: boolean;\n}\n\nexport default function ToolContent<T extends FormikValues, I>({\n  title,\n  inputComponent,\n  resultComponent,\n  initialValues,\n  getGroups,\n  compute,\n  toolInfo,\n  exampleCards,\n  input,\n  setInput,\n  validationSchema,\n  renderCustomInput,\n  onValuesChange,\n  verticalGroups\n}: ToolContentProps<T, I>) {\n  return (\n    <Box>\n      <Formik\n        initialValues={initialValues}\n        validationSchema={validationSchema}\n        onSubmit={() => {}}\n      >\n        {({ values, setFieldValue }) => {\n          return (\n            <>\n              <ToolInputAndResult\n                input={\n                  inputComponent ??\n                  (renderCustomInput &&\n                    renderCustomInput(values, setFieldValue))\n                }\n                result={resultComponent}\n              />\n              <FormikListenerComponent<T>\n                compute={compute}\n                input={input}\n                onValuesChange={onValuesChange}\n              />\n              <ToolOptions getGroups={getGroups} vertical={verticalGroups} />\n\n              {toolInfo && toolInfo.title && toolInfo.description && (\n                <ToolInfo\n                  title={toolInfo.title}\n                  description={toolInfo.description}\n                />\n              )}\n\n              {exampleCards && exampleCards.length > 0 && (\n                <>\n                  <Separator backgroundColor=\"#5581b5\" margin=\"50px\" />\n                  <ToolExamples\n                    title={title}\n                    exampleCards={exampleCards}\n                    getGroups={getGroups}\n                    setInput={setInput}\n                  />\n                </>\n              )}\n            </>\n          );\n        }}\n      </Formik>\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/ToolHeader.tsx",
    "content": "import { Box, Button, Stack, styled, useTheme } from '@mui/material';\nimport Typography from '@mui/material/Typography';\nimport ToolBreadcrumb from './ToolBreadcrumb';\nimport { capitalizeFirstLetter } from '../utils/string';\nimport Grid from '@mui/material/Grid';\nimport { Icon, IconifyIcon } from '@iconify/react';\nimport { categoriesColors } from '../config/uiConfig';\nimport { getToolsByCategory } from '@tools/index';\nimport { useEffect, useState } from 'react';\nimport { isBookmarked, toggleBookmarked } from '@utils/bookmark';\nimport IconButton from '@mui/material/IconButton';\nimport { useTranslation } from 'react-i18next';\nimport useMediaQuery from '@mui/material/useMediaQuery';\nimport { validNamespaces } from '../i18n';\n\nconst StyledButton = styled(Button)(({ theme }) => ({\n  backgroundColor: 'white',\n  '&:hover': {\n    backgroundColor: theme.palette.primary.main,\n    color: 'white'\n  }\n}));\n\ninterface ToolHeaderProps {\n  title: string;\n  description: string;\n  icon?: IconifyIcon | string;\n  type: string;\n  path: string;\n}\n\nfunction ToolLinks() {\n  const { t } = useTranslation();\n  const [examplesVisible, setExamplesVisible] = useState(false);\n  const theme = useTheme();\n  const isMd = useMediaQuery(theme.breakpoints.down('md'));\n\n  useEffect(() => {\n    const timeout = setTimeout(() => {\n      const element = document.getElementById('examples');\n      if (element && isVisible(element)) {\n        setExamplesVisible(true);\n      }\n    }, 500);\n\n    return () => clearTimeout(timeout);\n  }, []);\n\n  const scrollToElement = (id: string) => {\n    document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });\n  };\n  function isVisible(elm: HTMLElement | null) {\n    return !!elm;\n  }\n  return (\n    <Grid container spacing={2} mt={1}>\n      {isMd && (\n        <Grid item md={12} lg={6}>\n          <StyledButton\n            sx={{ backgroundColor: 'background.paper' }}\n            fullWidth\n            variant=\"outlined\"\n            onClick={() => scrollToElement('tool')}\n          >\n            Use This Tool\n          </StyledButton>\n        </Grid>\n      )}\n      {examplesVisible && (\n        <Grid item md={12} lg={6}>\n          <StyledButton\n            fullWidth\n            variant=\"outlined\"\n            sx={{ backgroundColor: 'background.paper' }}\n            onClick={() => scrollToElement('examples')}\n          >\n            {t('toolHeader.seeExamples')}\n          </StyledButton>\n        </Grid>\n      )}\n      {/*<Grid item md={12} lg={4}>*/}\n      {/*  <StyledButton fullWidth variant=\"outlined\" href=\"#tour\">*/}\n      {/*    Learn How to Use*/}\n      {/*  </StyledButton>*/}\n      {/*</Grid>*/}\n    </Grid>\n  );\n}\n\nexport default function ToolHeader({\n  icon,\n  title,\n  description,\n  type,\n  path\n}: ToolHeaderProps) {\n  const theme = useTheme();\n  const { t } = useTranslation();\n  const [bookmarked, setBookmarked] = useState<boolean>(isBookmarked(path));\n  return (\n    <Box my={4}>\n      <ToolBreadcrumb\n        items={[\n          { title: 'All tools', link: '/' },\n          {\n            title: getToolsByCategory([], t).find(\n              (category) => category.type === type\n            )!.rawTitle,\n            link: '/categories/' + type\n          },\n          { title }\n        ]}\n      />\n      <Grid mt={1} container spacing={2}>\n        <Grid item xs={12} md={8}>\n          <Stack direction={'row'} spacing={2} alignItems={'center'}>\n            <Typography mb={2} fontSize={30} color={'primary'}>\n              {title}\n            </Typography>\n            <IconButton\n              onClick={(e) => {\n                toggleBookmarked(path);\n                setBookmarked(!bookmarked);\n              }}\n            >\n              <Icon\n                fontSize={30}\n                color={\n                  bookmarked\n                    ? theme.palette.primary.main\n                    : theme.palette.grey[500]\n                }\n                icon={bookmarked ? 'mdi:bookmark' : 'mdi:bookmark-plus-outline'}\n              />\n            </IconButton>\n          </Stack>\n          <Typography fontSize={20}>{description}</Typography>\n          <ToolLinks />\n        </Grid>\n\n        {icon && (\n          <Grid item xs={12} md={4}>\n            <Box sx={{ display: 'flex', justifyContent: 'center' }}>\n              <Icon\n                icon={icon}\n                fontSize={'250'}\n                color={\n                  categoriesColors[\n                    Math.floor(Math.random() * categoriesColors.length)\n                  ]\n                }\n              />\n            </Box>\n          </Grid>\n        )}\n      </Grid>\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/ToolInfo.tsx",
    "content": "import { Box, Stack, Typography } from '@mui/material';\n\ninterface ExampleProps {\n  title: string;\n  description: string;\n}\n\nexport default function ToolInfo({ title, description }: ExampleProps) {\n  return (\n    <Stack direction={'row'} alignItems={'center'} spacing={2} mt={4}>\n      <Box>\n        <Typography mb={2} fontSize={30} color={'primary'}>\n          {title}\n        </Typography>\n        <Typography fontSize={20}>{description}</Typography>\n      </Box>\n    </Stack>\n  );\n}\n"
  },
  {
    "path": "src/components/ToolInputAndResult.tsx",
    "content": "import React, { ReactNode } from 'react';\nimport Grid from '@mui/material/Grid';\n\nexport default function ToolInputAndResult({\n  input,\n  result\n}: {\n  input?: ReactNode;\n  result?: ReactNode;\n}) {\n  if (input || result) {\n    return (\n      <Grid id=\"tool\" container spacing={2}>\n        {input && (\n          <Grid item xs={12} md={result ? 6 : 12}>\n            {input}\n          </Grid>\n        )}\n        <Grid item xs={12} md={input ? 6 : 12}>\n          {result}\n        </Grid>\n      </Grid>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/ToolLayout.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { ReactNode } from 'react';\nimport { Helmet } from 'react-helmet';\nimport ToolHeader from './ToolHeader';\nimport Separator from './Separator';\nimport AllTools from './allTools/AllTools';\nimport { getToolsByCategory } from '@tools/index';\nimport {\n  capitalizeFirstLetter,\n  getI18nNamespaceFromToolCategory\n} from '../utils/string';\nimport { IconifyIcon } from '@iconify/react';\nimport { useTranslation } from 'react-i18next';\nimport { ToolCategory } from '@tools/defineTool';\nimport { FullI18nKey } from '../i18n';\n\nexport default function ToolLayout({\n  children,\n  icon,\n  i18n,\n  type,\n  fullPath\n}: {\n  icon?: IconifyIcon | string;\n  type: ToolCategory;\n  fullPath: string;\n  children: ReactNode;\n  i18n?: {\n    name: FullI18nKey;\n    description: FullI18nKey;\n    shortDescription: FullI18nKey;\n  };\n}) {\n  const { t } = useTranslation([\n    'translation',\n    getI18nNamespaceFromToolCategory(type)\n  ]);\n\n  // Use i18n keys if available, otherwise fall back to provided strings\n  //@ts-ignore\n  const toolTitle: string = t(i18n.name);\n  //@ts-ignore\n  const toolDescription: string = t(i18n.description);\n\n  const otherCategoryTools =\n    getToolsByCategory([], t)\n      .find((category) => category.type === type)\n      ?.tools.filter((tool) => t(tool.name) !== toolTitle)\n      .map((tool) => ({\n        title: tool.name,\n        description: tool.shortDescription,\n        link: '/' + tool.path,\n        icon: tool.icon\n      })) ?? [];\n\n  return (\n    <Box\n      width={'100%'}\n      display={'flex'}\n      flexDirection={'column'}\n      alignItems={'center'}\n      sx={{ backgroundColor: 'background.default' }}\n    >\n      <Helmet>\n        <title>{`${toolTitle} - OmniTools`}</title>\n      </Helmet>\n      <Box width={'85%'}>\n        <ToolHeader\n          title={toolTitle}\n          description={toolDescription}\n          icon={icon}\n          type={type}\n          path={fullPath}\n        />\n        {children}\n        <Separator backgroundColor=\"#5581b5\" margin=\"50px\" />\n        <AllTools\n          title={t('translation:toolLayout.allToolsTitle', '', {\n            type: capitalizeFirstLetter(\n              getToolsByCategory([], t).find(\n                (category) => category.type === type\n              )!.title\n            )\n          })}\n          toolCards={otherCategoryTools}\n        />\n      </Box>\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/UserTypeFilter.tsx",
    "content": "import React from 'react';\nimport { Box, Chip } from '@mui/material';\nimport { UserType } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\n\ninterface UserTypeFilterProps {\n  selectedUserTypes: UserType[];\n  userTypes?: UserType[];\n  onUserTypesChange: (userTypes: UserType[]) => void;\n}\n\nexport default function UserTypeFilter({\n  selectedUserTypes,\n  onUserTypesChange,\n  userTypes = ['generalUsers', 'developers']\n}: UserTypeFilterProps) {\n  const { t } = useTranslation('translation');\n  if (userTypes.length <= 1) return null;\n  return (\n    <Box\n      sx={{\n        display: 'flex',\n        flexWrap: 'wrap',\n        gap: 1,\n        minWidth: 200,\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      {userTypes.map((userType) => (\n        <Chip\n          key={userType}\n          label={t(`userTypes.${userType}`)}\n          color={selectedUserTypes.includes(userType) ? 'primary' : 'default'}\n          onClick={() => {\n            const isSelected = selectedUserTypes.includes(userType);\n            const newUserTypes = isSelected\n              ? selectedUserTypes.filter((ut) => ut !== userType)\n              : [...selectedUserTypes, userType];\n            onUserTypesChange(newUserTypes);\n          }}\n          sx={{ cursor: 'pointer' }}\n        />\n      ))}\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/allTools/AllTools.tsx",
    "content": "import { Box, Grid, Stack, Typography } from '@mui/material';\nimport ToolCard from './ToolCard';\nimport { IconifyIcon } from '@iconify/react';\nimport { useTranslation } from 'react-i18next';\nimport { FullI18nKey } from '../../i18n';\n\nexport interface ToolCardProps {\n  title: FullI18nKey;\n  description: FullI18nKey;\n  link: string;\n  icon: IconifyIcon | string;\n}\n\ninterface AllToolsProps {\n  title: string;\n  toolCards: ToolCardProps[];\n}\n\nexport default function AllTools({ title, toolCards }: AllToolsProps) {\n  const { t } = useTranslation();\n  return (\n    <Box mt={4} mb={10}>\n      <Typography mb={2} fontSize={30} color={'primary'}>\n        {title}\n      </Typography>\n      <Stack direction={'row'} alignItems={'center'} spacing={2}>\n        <Grid container spacing={2}>\n          {toolCards.map((card, index) => (\n            <Grid item xs={12} md={6} lg={4} key={index}>\n              <ToolCard\n                //@ts-ignore\n                title={t(card.title)}\n                //@ts-ignore\n                description={t(card.description)}\n                link={card.link}\n                icon={card.icon}\n              />\n            </Grid>\n          ))}\n        </Grid>\n      </Stack>\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/allTools/ToolCard.tsx",
    "content": "import {\n  Box,\n  Card,\n  CardContent,\n  Link,\n  Stack,\n  Typography,\n  useTheme\n} from '@mui/material';\nimport { ToolCardProps } from './AllTools';\nimport ChevronRightIcon from '@mui/icons-material/ChevronRight';\nimport { useNavigate } from 'react-router-dom';\nimport { Icon } from '@iconify/react';\n\nexport default function ToolCard({\n  title,\n  description,\n  link,\n  icon\n}: ToolCardProps) {\n  const theme = useTheme();\n  const navigate = useNavigate();\n  return (\n    <Card\n      onClick={() => navigate(link)}\n      raised\n      sx={{\n        borderRadius: 2,\n        bgcolor: 'background.darkSecondary',\n        borderColor: 'background.darkSecondary',\n        color: '#fff',\n        boxShadow:\n          theme.palette.mode === 'dark'\n            ? null\n            : '6px 6px 12px #b8b9be, -6px -6px 12px #fff',\n        cursor: 'pointer',\n        height: '100%',\n        '&:hover': {\n          transform: 'scale(1.05)'\n        }\n      }}\n    >\n      <CardContent>\n        <Box\n          display=\"flex\"\n          justifyContent=\"space-between\"\n          sx={{\n            paddingBottom: 1,\n            borderBottomWidth: 1,\n            borderColor: '#ffffff70'\n          }}\n        >\n          <Stack direction={'row'} spacing={2} alignItems={'center'}>\n            <Icon icon={icon} fontSize={25} />\n            <Typography variant=\"h5\" component=\"h2\">\n              {title}\n            </Typography>\n          </Stack>\n          <Link href={link} underline=\"none\" sx={{ color: '#fff' }}>\n            <ChevronRightIcon />\n          </Link>\n        </Box>\n        <Typography variant=\"body2\" mt={2} color=\"#fff\">\n          {description}\n        </Typography>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "src/components/examples/ExampleCard.tsx",
    "content": "import {\n  Box,\n  Card,\n  CardContent,\n  Stack,\n  TextField,\n  Typography,\n  useTheme\n} from '@mui/material';\nimport ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';\nimport ExampleOptions from './ExampleOptions';\nimport { GetGroupsType } from '@components/options/ToolOptions';\n\nexport interface ExampleCardProps<T> {\n  title: string;\n  description: string;\n  sampleText?: string;\n  sampleResult: string;\n  sampleOptions: T;\n  changeInputResult: (newInput: string | undefined, newOptions: T) => void;\n  getGroups: GetGroupsType<T> | null;\n}\n\nexport default function ExampleCard<T>({\n  title,\n  description,\n  sampleText,\n  sampleResult,\n  sampleOptions,\n  changeInputResult,\n  getGroups\n}: ExampleCardProps<T>) {\n  const theme = useTheme();\n  return (\n    <Card\n      raised\n      onClick={() => {\n        changeInputResult(sampleText, sampleOptions);\n      }}\n      sx={{\n        bgcolor: 'background.lightSecondary',\n        height: '100%',\n        overflow: 'hidden',\n        borderRadius: 2,\n        transition: 'background-color 0.3s ease',\n        cursor: 'pointer',\n        '&:hover': {\n          boxShadow: `12px 9px 11px 2px ${\n            theme.palette.mode === 'dark' ? theme.palette.grey[900] : '#b8b9be'\n          }, -6px -6px 12px ${theme.palette.mode === 'dark' ? 'black' : '#fff'}`\n        }\n      }}\n    >\n      <CardContent>\n        <Box display=\"flex\" justifyContent=\"space-between\" borderRadius=\"5px\">\n          <Typography variant=\"h5\" component=\"h2\">\n            {title}\n          </Typography>\n        </Box>\n        <Stack direction={'column'} alignItems={'center'} spacing={2}>\n          <Typography variant=\"body2\" color=\"text.secondary\">\n            {description}\n          </Typography>\n\n          {sampleText && (\n            <Box\n              sx={{\n                display: 'flex',\n                zIndex: '2',\n                width: '100%',\n                height: '100%',\n                bgcolor: 'transparent',\n                padding: '5px 10px',\n                borderRadius: '5px',\n                boxShadow:\n                  'inset 2px 2px 5px #b8b9be, inset -3px -3px 7px #fff;'\n              }}\n            >\n              <TextField\n                value={sampleText}\n                disabled\n                fullWidth\n                multiline\n                sx={{\n                  '& .MuiOutlinedInput-root': {\n                    zIndex: '-1',\n                    '& fieldset': {\n                      border: 'none'\n                    }\n                  }\n                }}\n              />\n            </Box>\n          )}\n\n          <ArrowDownwardIcon />\n          <Box\n            sx={{\n              display: 'flex',\n              zIndex: '2',\n              width: '100%',\n              height: '100%',\n              bgcolor: 'transparent',\n              padding: '5px 10px',\n              borderRadius: '5px',\n              cursor: 'pointer',\n              boxShadow: 'inset 2px 2px 5px #b8b9be, inset -3px -3px 7px #fff;'\n            }}\n          >\n            <TextField\n              value={sampleResult}\n              disabled\n              fullWidth\n              multiline\n              sx={{\n                '& .MuiOutlinedInput-root': {\n                  zIndex: '-1',\n                  '& fieldset': {\n                    border: 'none'\n                  }\n                }\n              }}\n            />\n          </Box>\n\n          <ExampleOptions options={sampleOptions} getGroups={getGroups} />\n        </Stack>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "src/components/examples/ExampleOptions.tsx",
    "content": "import ToolOptionGroups from '@components/options/ToolOptionGroups';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport React from 'react';\n\nexport default function ExampleOptions<T>({\n  options,\n  getGroups\n}: {\n  options: T;\n  getGroups: GetGroupsType<T> | null;\n}) {\n  return (\n    <ToolOptionGroups\n      // @ts-ignore\n      groups={getGroups?.({ values: options }) ?? []}\n      vertical\n    />\n  );\n}\n"
  },
  {
    "path": "src/components/examples/ToolExamples.tsx",
    "content": "import { Box, Grid, Stack, Typography } from '@mui/material';\nimport ExampleCard, { ExampleCardProps } from './ExampleCard';\nimport React from 'react';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { useFormikContext } from 'formik';\nimport { useTranslation } from 'react-i18next';\n\nexport type CardExampleType<T> = Omit<\n  ExampleCardProps<T>,\n  'getGroups' | 'changeInputResult'\n>;\n\nexport interface ExampleProps<T> {\n  title: string;\n  subtitle?: string;\n  exampleCards: CardExampleType<T>[];\n  getGroups: GetGroupsType<T> | null;\n  setInput?: React.Dispatch<React.SetStateAction<any>>;\n}\n\nexport default function ToolExamples<T>({\n  title,\n  subtitle,\n  exampleCards,\n  getGroups,\n  setInput\n}: ExampleProps<T>) {\n  const { t } = useTranslation();\n  const { setValues } = useFormikContext<T>();\n\n  function changeInputResult(newInput: string | undefined, newOptions: T) {\n    setInput?.(newInput);\n    setValues(newOptions);\n    const toolsElement = document.getElementById('tool');\n    if (toolsElement) {\n      toolsElement.scrollIntoView({ behavior: 'smooth' });\n    }\n  }\n\n  return (\n    <Box id={'examples'} mt={4}>\n      <Box mt={4} display=\"flex\" gap={1} alignItems=\"center\">\n        <Typography mb={2} fontSize={30} color={'primary'}>\n          {t('toolExamples.title', { title })}\n        </Typography>\n        <Typography mb={2} fontSize={30} color={'secondary'}>\n          {subtitle ?? t('toolExamples.subtitle')}\n        </Typography>\n      </Box>\n\n      <Stack direction={'row'} alignItems={'center'} spacing={2}>\n        <Grid container spacing={2}>\n          {exampleCards.map((card, index) => (\n            <Grid item xs={12} md={6} lg={4} key={index}>\n              <ExampleCard\n                title={card.title}\n                description={card.description}\n                sampleText={card.sampleText}\n                sampleResult={card.sampleResult}\n                sampleOptions={card.sampleOptions}\n                getGroups={getGroups}\n                changeInputResult={changeInputResult}\n              />\n            </Grid>\n          ))}\n        </Grid>\n      </Stack>\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/index.css",
    "content": "a {\n  color: #1c76ce;\n}\n\na:hover {\n  color: #030362;\n}\n\n* {\n  font-family: Quicksand,sans-serif!important;\n}\n"
  },
  {
    "path": "src/components/input/BaseFileInput.tsx",
    "content": "import React, { ReactNode, useContext, useEffect, useState } from 'react';\nimport { Box, useTheme } from '@mui/material';\nimport Typography from '@mui/material/Typography';\nimport InputHeader from '../InputHeader';\nimport InputFooter from './InputFooter';\nimport {\n  BaseFileInputProps,\n  createObjectURL,\n  revokeObjectURL\n} from './file-input-utils';\nimport { globalInputHeight } from '../../config/uiConfig';\nimport { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';\nimport greyPattern from '@assets/grey-pattern.png';\nimport { isArray } from 'lodash';\nimport { useTranslation } from 'react-i18next';\n\ninterface BaseFileInputComponentProps extends BaseFileInputProps {\n  children: (props: { preview: string | undefined }) => ReactNode;\n  type: 'image' | 'video' | 'audio' | 'pdf';\n}\n\nexport default function BaseFileInput({\n  value,\n  onChange,\n  accept,\n  title,\n  children,\n  type\n}: BaseFileInputComponentProps) {\n  const { t } = useTranslation();\n  const [preview, setPreview] = useState<string | null>(null);\n  const [isDragging, setIsDragging] = useState<boolean>(false);\n  const theme = useTheme();\n  const fileInputRef = React.useRef<HTMLInputElement>(null);\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n\n  useEffect(() => {\n    if (value) {\n      try {\n        const objectUrl = createObjectURL(value);\n        setPreview(objectUrl);\n        return () => revokeObjectURL(objectUrl);\n      } catch (error) {\n        console.error('Error previewing file:', error);\n      }\n    } else setPreview(null);\n  }, [value]);\n\n  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    const file = event.target.files?.[0];\n    if (file) onChange(file);\n  };\n\n  const handleImportClick = () => {\n    fileInputRef.current?.click();\n  };\n\n  const handleCopy = () => {\n    if (isArray(value)) {\n      const blob = new Blob([value[0]], { type: value[0].type });\n      const clipboardItem = new ClipboardItem({ [value[0].type]: blob });\n\n      navigator.clipboard\n        .write([clipboardItem])\n        .then(() => showSnackBar(t('baseFileInput.fileCopied'), 'success'))\n        .catch((err) => {\n          showSnackBar(t('baseFileInput.copyFailed', { error: err }), 'error');\n        });\n    }\n  };\n\n  const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {\n    event.preventDefault();\n    event.stopPropagation();\n    setIsDragging(false);\n\n    if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {\n      const file = event.dataTransfer.files[0];\n\n      // Check if file type is acceptable\n      const isAcceptable = accept.some((acceptType) => {\n        // Handle wildcards like \"image/*\"\n        if (acceptType.endsWith('/*')) {\n          const category = acceptType.split('/')[0];\n          return file.type.startsWith(category);\n        }\n        return acceptType === file.type;\n      });\n\n      if (isAcceptable) {\n        onChange(file);\n      } else {\n        showSnackBar(\n          `Invalid file type. Please use ${accept.join(', ')}`,\n          'error'\n        );\n      }\n    }\n  };\n\n  const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {\n    event.preventDefault();\n    event.stopPropagation();\n  };\n\n  const handleDragEnter = (event: React.DragEvent<HTMLDivElement>) => {\n    event.preventDefault();\n    event.stopPropagation();\n    setIsDragging(true);\n  };\n\n  const handleDragLeave = (event: React.DragEvent<HTMLDivElement>) => {\n    event.preventDefault();\n    event.stopPropagation();\n    setIsDragging(false);\n  };\n\n  const handleClear = () => {\n    onChange(null);\n    setPreview(null);\n    if (fileInputRef.current) {\n      fileInputRef.current.value = '';\n    }\n  };\n\n  useEffect(() => {\n    const handlePaste = (event: ClipboardEvent) => {\n      const clipboardItems = event.clipboardData?.items ?? [];\n      const item = clipboardItems[0];\n      if (\n        item &&\n        (item.type.includes('image') || item.type.includes('video'))\n      ) {\n        const file = item.getAsFile();\n        if (file) onChange(file);\n      }\n    };\n    window.addEventListener('paste', handlePaste);\n\n    return () => {\n      window.removeEventListener('paste', handlePaste);\n    };\n  }, [onChange]);\n\n  return (\n    <Box>\n      <InputHeader\n        title={title || 'Input ' + type.charAt(0).toUpperCase() + type.slice(1)}\n      />\n      <Box\n        sx={{\n          width: '100%',\n          height: globalInputHeight,\n          border: preview ? 0 : 1,\n          borderRadius: 2,\n          boxShadow: '5',\n          bgcolor: 'background.paper',\n          position: 'relative',\n          borderColor: isDragging ? theme.palette.primary.main : undefined,\n          borderWidth: isDragging ? 2 : 1,\n          borderStyle: isDragging ? 'dashed' : 'solid'\n        }}\n        onDrop={handleDrop}\n        onDragOver={handleDragOver}\n        onDragEnter={handleDragEnter}\n        onDragLeave={handleDragLeave}\n      >\n        {preview ? (\n          <Box\n            width=\"100%\"\n            height=\"100%\"\n            sx={{\n              display: 'flex',\n              alignItems: 'center',\n              justifyContent: 'center',\n              backgroundImage:\n                theme.palette.mode === 'dark' ? null : `url(${greyPattern})`,\n              position: 'relative',\n              overflow: 'hidden'\n            }}\n          >\n            {children({ preview })}\n          </Box>\n        ) : (\n          <Box\n            onClick={handleImportClick}\n            sx={{\n              display: 'flex',\n              flexDirection: 'column',\n              alignItems: 'center',\n              justifyContent: 'center',\n              padding: 5,\n              height: '100%',\n              cursor: 'pointer'\n            }}\n          >\n            {isDragging ? (\n              <Typography\n                color={theme.palette.primary.main}\n                variant=\"h6\"\n                align=\"center\"\n              >\n                {t('baseFileInput.dropFileHere', { type })}\n              </Typography>\n            ) : (\n              <Typography\n                color={\n                  theme.palette.mode === 'dark'\n                    ? theme.palette.grey['300']\n                    : theme.palette.grey['600']\n                }\n              >\n                {t('baseFileInput.selectFileDescription', { type })}\n              </Typography>\n            )}\n          </Box>\n        )}\n      </Box>\n      <InputFooter\n        handleCopy={handleCopy}\n        handleImport={handleImportClick}\n        handleClear={value ? handleClear : undefined}\n      />\n      <input\n        ref={fileInputRef}\n        style={{ display: 'none' }}\n        type=\"file\"\n        accept={accept.join(',')}\n        onChange={handleFileChange}\n        multiple={false}\n      />\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/input/InputFooter.tsx",
    "content": "import { Stack } from '@mui/material';\nimport Button from '@mui/material/Button';\nimport PublishIcon from '@mui/icons-material/Publish';\nimport ContentPasteIcon from '@mui/icons-material/ContentPaste';\nimport ClearIcon from '@mui/icons-material/Clear';\nimport { useTranslation } from 'react-i18next';\n\nexport default function InputFooter({\n  handleImport,\n  handleCopy,\n  handleClear\n}: {\n  handleImport: () => void;\n  handleCopy?: () => void;\n  handleClear?: () => void;\n}) {\n  const { t } = useTranslation();\n\n  return (\n    <Stack mt={1} direction={'row'} spacing={2}>\n      <Button onClick={handleImport} startIcon={<PublishIcon />}>\n        {t('inputFooter.importFromFile')}\n      </Button>\n      {handleCopy && (\n        <Button onClick={handleCopy} startIcon={<ContentPasteIcon />}>\n          {t('inputFooter.copyToClipboard')}\n        </Button>\n      )}\n      {handleClear && (\n        <Button onClick={handleClear} startIcon={<ClearIcon />}>\n          {t('inputFooter.clear')}\n        </Button>\n      )}\n    </Stack>\n  );\n}\n"
  },
  {
    "path": "src/components/input/NumericInputWithUnit.tsx",
    "content": "import React, { useState, useEffect } from 'react';\nimport { Grid, Select, MenuItem } from '@mui/material';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport Qty from 'js-quantities';\nimport { useTranslation } from 'react-i18next';\n//\n\nconst siPrefixes: { [key: string]: number } = {\n  'Default prefix': 1,\n  k: 1000,\n  M: 1000000,\n  G: 1000000000,\n  T: 1000000000000,\n  m: 0.001,\n  u: 0.000001,\n  n: 0.000000001,\n  p: 0.000000000001\n};\n\nexport default function NumericInputWithUnit(props: {\n  value: { value: number; unit: string };\n  disabled?: boolean;\n  disableChangingUnit?: boolean;\n  onOwnChange?: (value: { value: number; unit: string }) => void;\n  defaultPrefix?: string;\n}) {\n  const { t } = useTranslation();\n  const [inputValue, setInputValue] = useState(props.value.value);\n  const [prefix, setPrefix] = useState(props.defaultPrefix || 'Default prefix');\n\n  // internal display unit\n  const [unit, setUnit] = useState('');\n\n  // Whether user has overridden the unit\n  const [userSelectedUnit, setUserSelectedUnit] = useState(false);\n  const [unitKind, setUnitKind] = useState('');\n  const [unitOptions, setUnitOptions] = useState<string[]>([]);\n\n  const [disabled, setDisabled] = useState(props.disabled);\n  const [disableChangingUnit, setDisableChangingUnit] = useState(\n    props.disableChangingUnit\n  );\n\n  useEffect(() => {\n    setDisabled(props.disabled);\n    setDisableChangingUnit(props.disableChangingUnit);\n  }, [props.disabled, props.disableChangingUnit]);\n\n  useEffect(() => {\n    if (unitKind != Qty(props.value.unit).kind()) {\n      // Update the options for what units similar to this one are available\n      const kind = Qty(props.value.unit).kind();\n      let units: string[] = [];\n      if (kind) {\n        units = Qty.getUnits(kind);\n      }\n\n      if (!units.includes(props.value.unit)) {\n        units.push(props.value.unit);\n      }\n\n      // Workaround because the lib doesn't list them\n      if (kind == 'area') {\n        units.push('km^2');\n        units.push('mile^2');\n        units.push('inch^2');\n        units.push('m^2');\n        units.push('cm^2');\n      }\n      setUnitOptions(units);\n      setInputValue(props.value.value);\n      setUnit(props.value.unit);\n      setUnitKind(kind);\n      setUserSelectedUnit(false);\n      return;\n    }\n\n    if (userSelectedUnit) {\n      if (!isNaN(props.value.value)) {\n        const converted = Qty(props.value.value, props.value.unit).to(\n          unit\n        ).scalar;\n        setInputValue(converted);\n      } else {\n        setInputValue(props.value.value);\n      }\n    } else {\n      setInputValue(props.value.value);\n      setUnit(props.value.unit);\n    }\n  }, [props.value.value, props.value.unit, unit]);\n\n  const handleUserValueChange = (newValue: number) => {\n    setInputValue(newValue);\n\n    if (props.onOwnChange) {\n      try {\n        const converted = Qty(newValue * siPrefixes[prefix], unit).to(\n          props.value.unit\n        ).scalar;\n\n        props.onOwnChange({ unit: props.value.unit, value: converted });\n      } catch (error) {\n        console.error('Conversion error', error);\n      }\n    }\n  };\n\n  const handlePrefixChange = (newPrefix: string) => {\n    setPrefix(newPrefix);\n  };\n\n  const handleUserUnitChange = (newUnit: string) => {\n    if (!newUnit) return;\n    const oldInputValue = inputValue;\n    const oldUnit = unit;\n    setUnit(newUnit);\n    setPrefix('Default prefix');\n\n    const convertedValue = Qty(oldInputValue * siPrefixes[prefix], oldUnit).to(\n      newUnit\n    ).scalar;\n    setInputValue(convertedValue);\n  };\n\n  return (\n    <Grid container spacing={2} alignItems=\"center\">\n      <Grid item xs={12} md={4}>\n        <TextFieldWithDesc\n          disabled={disabled}\n          type=\"number\"\n          fullWidth\n          sx={{ width: { xs: '75%', sm: '80%', md: '90%' } }}\n          value={(inputValue / siPrefixes[prefix])\n            .toFixed(9)\n            .replace(/(\\d*\\.\\d+?)0+$/, '$1')}\n          onOwnChange={(value) => handleUserValueChange(parseFloat(value))}\n        />\n      </Grid>\n\n      <Grid item xs={12} md={3}>\n        <Select\n          fullWidth\n          disabled={disableChangingUnit}\n          value={prefix}\n          sx={{ width: { xs: '75%', sm: '80%', md: '90%' } }}\n          onChange={(evt) => {\n            handlePrefixChange(evt.target.value || '');\n          }}\n        >\n          {Object.keys(siPrefixes).map((key) => (\n            <MenuItem key={key} value={key}>\n              {key}\n            </MenuItem>\n          ))}\n        </Select>\n      </Grid>\n\n      <Grid item xs={12} md={5}>\n        <Select\n          fullWidth\n          disabled={disableChangingUnit}\n          placeholder={t('numericInputWithUnit.unit')}\n          sx={{ width: { xs: '75%', sm: '80%', md: '90%' } }}\n          value={unit}\n          onChange={(event) => {\n            setUserSelectedUnit(true);\n            handleUserUnitChange(event.target.value || '');\n          }}\n        >\n          {unitOptions.map((key) => (\n            <MenuItem key={key} value={key}>\n              {key}\n            </MenuItem>\n          ))}\n        </Select>\n      </Grid>\n    </Grid>\n  );\n}\n"
  },
  {
    "path": "src/components/input/ToolAudioInput.tsx",
    "content": "import React, { useRef } from 'react';\nimport { Box, Typography } from '@mui/material';\nimport BaseFileInput from './BaseFileInput';\nimport { BaseFileInputProps } from './file-input-utils';\nimport { AUDIO_FORMATS } from 'pages/tools/converters/audio-converter/types';\n\ninterface AudioFileInputProps extends Omit<BaseFileInputProps, 'accept'> {\n  accept?: string[];\n}\n\nconst AUDIO_ACCEPT_TYPES = [\n  'audio/*',\n  ...Object.keys(AUDIO_FORMATS).map((format) => `.${format}`)\n];\n\nexport default function ToolAudioInput({\n  accept = AUDIO_ACCEPT_TYPES,\n  ...props\n}: AudioFileInputProps) {\n  const audioRef = useRef<HTMLAudioElement>(null);\n\n  return (\n    <BaseFileInput {...props} type={'audio'} accept={accept}>\n      {({ preview }) => (\n        <Box\n          sx={{\n            position: 'relative',\n            width: '100%',\n            height: '100%',\n            display: 'flex',\n            flexDirection: 'column',\n            alignItems: 'center',\n            justifyContent: 'center'\n          }}\n        >\n          {preview ? (\n            <audio\n              ref={audioRef}\n              src={preview}\n              style={{ maxWidth: '100%' }}\n              controls\n            />\n          ) : (\n            <Typography variant=\"body2\" color=\"textSecondary\">\n              Drag & drop or import an audio file\n            </Typography>\n          )}\n        </Box>\n      )}\n    </BaseFileInput>\n  );\n}\n"
  },
  {
    "path": "src/components/input/ToolCodeInput.tsx",
    "content": "import { Box, useTheme } from '@mui/material';\nimport React, { useContext, useRef } from 'react';\nimport { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';\nimport InputHeader from '../InputHeader';\nimport InputFooter from './InputFooter';\nimport { useTranslation } from 'react-i18next';\nimport Editor from '@monaco-editor/react';\nimport {\n  globalInputHeight,\n  codeInputHeightOffset\n} from '../../config/uiConfig';\n\nexport default function ToolCodeInput({\n  value,\n  onChange,\n  title = 'Input text',\n  language\n}: {\n  title?: string;\n  value: string;\n  language: string;\n  onChange: (value: string) => void;\n}) {\n  const { t } = useTranslation();\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n  const fileInputRef = useRef<HTMLInputElement>(null);\n  const theme = useTheme();\n\n  const handleCopy = () => {\n    navigator.clipboard\n      .writeText(value)\n      .then(() => showSnackBar(t('toolTextInput.copied'), 'success'))\n      .catch((err) => {\n        showSnackBar(t('toolTextInput.copyFailed', { error: err }), 'error');\n      });\n  };\n\n  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    const file = event.target.files?.[0];\n    if (file) {\n      const reader = new FileReader();\n      reader.onload = (e) => {\n        const text = e.target?.result;\n        if (typeof text === 'string') {\n          onChange(text);\n        }\n      };\n      reader.readAsText(file);\n    }\n  };\n\n  const handleImportClick = () => {\n    fileInputRef.current?.click();\n  };\n\n  return (\n    <Box>\n      <InputHeader title={title || t('toolTextInput.input')} />\n      <Box\n        height={`${globalInputHeight + codeInputHeightOffset}px`} // The +codeInputHeightOffset compensates for internal padding/border differences between Monaco Editor and MUI TextField\n        sx={{\n          display: 'flex',\n          flexDirection: 'column'\n        }}\n      >\n        <Box\n          sx={(theme) => ({\n            height: '100%',\n            display: 'flex',\n            flexDirection: 'column',\n            backgroundColor: 'background.paper',\n            '.monaco-editor': {\n              height: '100% !important',\n              outline: 'none !important',\n              '.overflow-guard': {\n                height: '100% !important',\n                border:\n                  theme.palette.mode === 'light'\n                    ? '1px solid rgba(0, 0, 0, 0.23)'\n                    : '1px solid rgba(255, 255, 255, 0.23)',\n                borderRadius: 1,\n                transition: theme.transitions.create(\n                  ['border-color', 'background-color'],\n                  {\n                    duration: theme.transitions.duration.shorter\n                  }\n                )\n              },\n              '&:hover .overflow-guard': {\n                borderColor: theme.palette.text.primary\n              }\n            },\n            '.decorationsOverviewRuler': {\n              display: 'none !important'\n            }\n          })}\n        >\n          <Editor\n            height=\"100%\"\n            language={language}\n            theme={theme.palette.mode === 'dark' ? 'vs-dark' : 'light'}\n            value={value}\n            onChange={(value) => onChange(value ?? '')}\n            options={{\n              scrollbar: {\n                vertical: 'visible',\n                horizontal: 'visible',\n                verticalScrollbarSize: 10,\n                horizontalScrollbarSize: 10,\n                alwaysConsumeMouseWheel: false\n              }\n            }}\n          />\n        </Box>\n        <InputFooter handleCopy={handleCopy} handleImport={handleImportClick} />\n        <input\n          type=\"file\"\n          accept=\"*\"\n          ref={fileInputRef}\n          style={{ display: 'none' }}\n          onChange={handleFileChange}\n        />\n      </Box>\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/input/ToolImageInput.tsx",
    "content": "import React, { useEffect, useRef, useState } from 'react';\nimport { Box } from '@mui/material';\nimport ReactCrop, { Crop, PixelCrop } from 'react-image-crop';\nimport 'react-image-crop/dist/ReactCrop.css';\nimport BaseFileInput from './BaseFileInput';\nimport { BaseFileInputProps } from './file-input-utils';\nimport { globalInputHeight } from '../../config/uiConfig';\n\ninterface ImageFileInputProps extends BaseFileInputProps {\n  showCropOverlay?: boolean;\n  cropShape?: 'rectangular' | 'circular';\n  cropPosition?: { x: number; y: number };\n  cropSize?: { width: number; height: number };\n  onCropChange?: (\n    position: { x: number; y: number },\n    size: { width: number; height: number }\n  ) => void;\n}\n\nexport default function ToolImageInput({\n  showCropOverlay = false,\n  cropShape = 'rectangular',\n  cropPosition = { x: 0, y: 0 },\n  cropSize = { width: 100, height: 100 },\n  onCropChange,\n  ...props\n}: ImageFileInputProps) {\n  const imageRef = useRef<HTMLImageElement>(null);\n  const [imgWidth, setImgWidth] = useState(0);\n  const [imgHeight, setImgHeight] = useState(0);\n\n  const [crop, setCrop] = useState<Crop>({\n    unit: 'px',\n    x: 0,\n    y: 0,\n    width: 0,\n    height: 0\n  });\n\n  const RATIO = imageRef.current ? imgWidth / imageRef.current.width : 1;\n\n  const onImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n    const { naturalWidth: width, naturalHeight: height } = e.currentTarget;\n    setImgWidth(width);\n    setImgHeight(height);\n\n    if (!crop.width && !crop.height && onCropChange) {\n      const initialCrop: Crop = {\n        unit: 'px',\n        x: Math.floor(width / 4),\n        y: Math.floor(height / 4),\n        width: Math.floor(width / 2),\n        height: Math.floor(height / 2)\n      };\n\n      setCrop(initialCrop);\n\n      onCropChange(\n        { x: initialCrop.x, y: initialCrop.y },\n        { width: initialCrop.width, height: initialCrop.height }\n      );\n    }\n  };\n  useEffect(() => {\n    if (\n      imgWidth &&\n      imgHeight &&\n      (cropPosition.x !== 0 ||\n        cropPosition.y !== 0 ||\n        cropSize.width !== 100 ||\n        cropSize.height !== 100)\n    ) {\n      setCrop({\n        unit: 'px',\n        x: cropPosition.x / RATIO,\n        y: cropPosition.y / RATIO,\n        width: cropSize.width / RATIO,\n        height: cropSize.height / RATIO\n      });\n    }\n  }, [cropPosition, cropSize, imgWidth, imgHeight, RATIO]);\n\n  const handleCropChange = (newCrop: Crop) => {\n    setCrop(newCrop);\n  };\n\n  const handleCropComplete = (crop: PixelCrop) => {\n    if (onCropChange) {\n      onCropChange(\n        { x: Math.round(crop.x * RATIO), y: Math.round(crop.y * RATIO) },\n        {\n          width: Math.round(crop.width * RATIO),\n          height: Math.round(crop.height * RATIO)\n        }\n      );\n    }\n  };\n\n  return (\n    <BaseFileInput {...props} type={'image'}>\n      {({ preview }) => (\n        <Box\n          width=\"100%\"\n          height=\"100%\"\n          sx={{\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center',\n            position: 'relative',\n            overflow: 'hidden'\n          }}\n        >\n          {showCropOverlay ? (\n            <ReactCrop\n              crop={crop}\n              onChange={handleCropChange}\n              onComplete={handleCropComplete}\n              circularCrop={cropShape === 'circular'}\n              style={{ maxWidth: '100%', maxHeight: globalInputHeight }}\n            >\n              <img\n                ref={imageRef}\n                src={preview}\n                alt=\"Preview\"\n                style={{ maxWidth: '100%', maxHeight: globalInputHeight }}\n                onLoad={onImageLoad}\n              />\n            </ReactCrop>\n          ) : (\n            <img\n              ref={imageRef}\n              src={preview}\n              alt=\"Preview\"\n              style={{ maxWidth: '100%', maxHeight: globalInputHeight }}\n              onLoad={onImageLoad}\n            />\n          )}\n        </Box>\n      )}\n    </BaseFileInput>\n  );\n}\n"
  },
  {
    "path": "src/components/input/ToolMultipleAudioInput.tsx",
    "content": "import { ReactNode, useContext, useEffect, useRef, useState } from 'react';\nimport { Box, useTheme } from '@mui/material';\nimport Typography from '@mui/material/Typography';\nimport InputHeader from '../InputHeader';\nimport InputFooter from './InputFooter';\nimport { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';\nimport { isArray } from 'lodash';\nimport MusicNoteIcon from '@mui/icons-material/MusicNote';\nimport { useTranslation } from 'react-i18next';\n\ninterface MultiAudioInputComponentProps {\n  accept: string[];\n  title?: string;\n  type: 'audio';\n  value: MultiAudioInput[];\n  onChange: (file: MultiAudioInput[]) => void;\n}\n\nexport interface MultiAudioInput {\n  file: File;\n  order: number;\n}\n\nexport default function ToolMultipleAudioInput({\n  value,\n  onChange,\n  accept,\n  title,\n  type\n}: MultiAudioInputComponentProps) {\n  const { t } = useTranslation();\n  const theme = useTheme();\n  const fileInputRef = useRef<HTMLInputElement>(null);\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n\n  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    const files = event.target.files;\n    if (files)\n      onChange([\n        ...value,\n        ...Array.from(files).map((file) => ({ file, order: value.length }))\n      ]);\n  };\n\n  const handleImportClick = () => {\n    fileInputRef.current?.click();\n  };\n\n  function handleClear() {\n    onChange([]);\n  }\n\n  function fileNameTruncate(fileName: string) {\n    const maxLength = 15;\n    if (fileName.length > maxLength) {\n      return fileName.slice(0, maxLength) + '...';\n    }\n    return fileName;\n  }\n\n  const sortList = () => {\n    const list = [...value];\n    list.sort((a, b) => a.order - b.order);\n    onChange(list);\n  };\n\n  const reorderList = (sourceIndex: number, destinationIndex: number) => {\n    if (destinationIndex === sourceIndex) {\n      return;\n    }\n    const list = [...value];\n\n    if (destinationIndex === 0) {\n      list[sourceIndex].order = list[0].order - 1;\n      sortList();\n      return;\n    }\n\n    if (destinationIndex === list.length - 1) {\n      list[sourceIndex].order = list[list.length - 1].order + 1;\n      sortList();\n      return;\n    }\n\n    if (destinationIndex < sourceIndex) {\n      list[sourceIndex].order =\n        (list[destinationIndex].order + list[destinationIndex - 1].order) / 2;\n      sortList();\n      return;\n    }\n\n    list[sourceIndex].order =\n      (list[destinationIndex].order + list[destinationIndex + 1].order) / 2;\n    sortList();\n  };\n\n  return (\n    <Box>\n      <InputHeader\n        title={\n          title ||\n          t('toolMultipleAudioInput.inputTitle', {\n            type: type.charAt(0).toUpperCase() + type.slice(1)\n          })\n        }\n      />\n      <Box\n        sx={{\n          width: '100%',\n          height: '300px',\n          border: value?.length ? 0 : 1,\n          borderRadius: 2,\n          boxShadow: '5',\n          bgcolor: 'background.paper',\n          position: 'relative'\n        }}\n      >\n        <Box\n          width=\"100%\"\n          height=\"100%\"\n          sx={{\n            overflow: 'auto',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center',\n            flexWrap: 'wrap',\n            position: 'relative'\n          }}\n        >\n          {value?.length ? (\n            value.map((file, index) => (\n              <Box\n                key={index}\n                sx={{\n                  margin: 1,\n                  display: 'flex',\n                  alignItems: 'center',\n                  justifyContent: 'space-between',\n                  width: '200px',\n                  border: 1,\n                  borderRadius: 1,\n                  padding: 1\n                }}\n              >\n                <Box sx={{ display: 'flex', alignItems: 'center' }}>\n                  <MusicNoteIcon />\n                  <Typography sx={{ marginLeft: 1 }}>\n                    {fileNameTruncate(file.file.name)}\n                  </Typography>\n                </Box>\n                <Box\n                  sx={{ cursor: 'pointer' }}\n                  onClick={() => {\n                    const updatedFiles = value.filter((_, i) => i !== index);\n                    onChange(updatedFiles);\n                  }}\n                >\n                  ✖\n                </Box>\n              </Box>\n            ))\n          ) : (\n            <Typography variant=\"body2\" color=\"text.secondary\">\n              {t('toolMultipleAudioInput.noFilesSelected')}\n            </Typography>\n          )}\n        </Box>\n      </Box>\n\n      <InputFooter handleImport={handleImportClick} handleClear={handleClear} />\n      <input\n        ref={fileInputRef}\n        style={{ display: 'none' }}\n        type=\"file\"\n        accept={accept.join(',')}\n        onChange={handleFileChange}\n        multiple={true}\n      />\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/input/ToolMultipleImageInput.tsx",
    "content": "import React, { useRef } from 'react';\nimport { Box } from '@mui/material';\nimport Typography from '@mui/material/Typography';\nimport InputHeader from '../InputHeader';\nimport InputFooter from './InputFooter';\nimport ImageIcon from '@mui/icons-material/Image';\nimport { useTranslation } from 'react-i18next';\n\ninterface MultiImageInputComponentProps {\n  accept: string[];\n  title?: string;\n  type: 'image';\n  value: MultiImageInput[];\n  onChange: (files: MultiImageInput[]) => void;\n}\n\nexport interface MultiImageInput {\n  file: File;\n  order: number;\n  preview?: string;\n}\n\nexport default function ToolMultiImageInput({\n  value,\n  onChange,\n  accept,\n  title,\n  type\n}: MultiImageInputComponentProps) {\n  const { t } = useTranslation();\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    const files = event.target.files;\n    if (files) {\n      const newFiles: MultiImageInput[] = Array.from(files).map((file) => ({\n        file,\n        order: value.length,\n        preview: URL.createObjectURL(file)\n      }));\n      onChange([...value, ...newFiles]);\n    }\n  };\n\n  const handleImportClick = () => {\n    fileInputRef.current?.click();\n  };\n\n  function handleClear() {\n    onChange([]);\n  }\n\n  function fileNameTruncate(fileName: string) {\n    const maxLength = 10;\n    if (fileName.length > maxLength) {\n      return fileName.slice(0, maxLength) + '...';\n    }\n    return fileName;\n  }\n\n  return (\n    <Box>\n      <InputHeader\n        title={\n          title ||\n          t('toolMultipleImageInput.inputTitle', {\n            type: type.charAt(0).toUpperCase() + type.slice(1)\n          })\n        }\n      />\n      <Box\n        sx={{\n          width: '100%',\n          height: '300px',\n          border: value?.length ? 0 : 1,\n          borderRadius: 2,\n          boxShadow: '5',\n          bgcolor: 'background.paper',\n          position: 'relative'\n        }}\n      >\n        <Box\n          width=\"100%\"\n          height=\"100%\"\n          sx={{\n            overflow: 'auto',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center',\n            flexWrap: 'wrap',\n            position: 'relative'\n          }}\n        >\n          {value?.length ? (\n            value.map((file, index) => (\n              <Box\n                key={index}\n                sx={{\n                  margin: 1,\n                  display: 'flex',\n                  flexDirection: 'column',\n                  alignItems: 'center',\n                  justifyContent: 'space-between',\n                  width: '120px',\n                  border: 1,\n                  borderRadius: 1,\n                  padding: 1,\n                  position: 'relative'\n                }}\n              >\n                {file.preview ? (\n                  <Box\n                    component=\"img\"\n                    src={file.preview}\n                    alt={file.file.name}\n                    sx={{\n                      width: '100%',\n                      height: '80px',\n                      objectFit: 'cover',\n                      borderRadius: 1\n                    }}\n                  />\n                ) : (\n                  <ImageIcon sx={{ fontSize: 40 }} />\n                )}\n                <Box\n                  sx={{\n                    display: 'flex',\n                    alignItems: 'center',\n                    justifyContent: 'space-between',\n                    width: '100%',\n                    mt: 0.5\n                  }}\n                >\n                  <Typography variant=\"caption\">\n                    {fileNameTruncate(file.file.name)}\n                  </Typography>\n                  <Box\n                    sx={{ cursor: 'pointer' }}\n                    onClick={() => {\n                      const updatedFiles = value.filter((_, i) => i !== index);\n                      onChange(updatedFiles);\n                    }}\n                  >\n                    ✖\n                  </Box>\n                </Box>\n              </Box>\n            ))\n          ) : (\n            <Typography variant=\"body2\" color=\"text.secondary\">\n              {t('toolMultipleImageInput.noFilesSelected')}\n            </Typography>\n          )}\n        </Box>\n      </Box>\n\n      <InputFooter handleImport={handleImportClick} handleClear={handleClear} />\n      <input\n        ref={fileInputRef}\n        style={{ display: 'none' }}\n        type=\"file\"\n        accept={accept.join(',')}\n        onChange={handleFileChange}\n        multiple={true}\n      />\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/input/ToolMultiplePdfInput.tsx",
    "content": "import { ReactNode, useContext, useEffect, useRef, useState } from 'react';\nimport { Box, useTheme } from '@mui/material';\nimport Typography from '@mui/material/Typography';\nimport InputHeader from '../InputHeader';\nimport InputFooter from './InputFooter';\nimport { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';\nimport { isArray } from 'lodash';\nimport PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';\nimport { useTranslation } from 'react-i18next';\n\ninterface MultiPdfInputComponentProps {\n  accept: string[];\n  title?: string;\n  type: 'pdf';\n  value: MultiPdfInput[];\n  onChange: (file: MultiPdfInput[]) => void;\n}\n\nexport interface MultiPdfInput {\n  file: File;\n  order: number;\n}\n\nexport default function ToolMultiFileInput({\n  value,\n  onChange,\n  accept,\n  title,\n  type\n}: MultiPdfInputComponentProps) {\n  const { t } = useTranslation();\n  const theme = useTheme();\n  const fileInputRef = useRef<HTMLInputElement>(null);\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n\n  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    const files = event.target.files;\n    if (files)\n      onChange([\n        ...value,\n        ...Array.from(files).map((file) => ({ file, order: value.length }))\n      ]);\n  };\n\n  const handleImportClick = () => {\n    fileInputRef.current?.click();\n  };\n\n  function handleClear() {\n    onChange([]);\n  }\n\n  function fileNameTruncate(fileName: string) {\n    const maxLength = 10;\n    if (fileName.length > maxLength) {\n      return fileName.slice(0, maxLength) + '...';\n    }\n    return fileName;\n  }\n\n  const sortList = () => {\n    const list = [...value];\n    list.sort((a, b) => a.order - b.order);\n    onChange(list);\n  };\n\n  const reorderList = (sourceIndex: number, destinationIndex: number) => {\n    console.log(sourceIndex, destinationIndex);\n    if (destinationIndex === sourceIndex) {\n      return;\n    }\n    const list = [...value];\n\n    if (destinationIndex === 0) {\n      list[sourceIndex].order = list[0].order - 1;\n      sortList();\n      return;\n    }\n\n    if (destinationIndex === list.length - 1) {\n      list[sourceIndex].order = list[list.length - 1].order + 1;\n      sortList();\n      return;\n    }\n\n    if (destinationIndex < sourceIndex) {\n      list[sourceIndex].order =\n        (list[destinationIndex].order + list[destinationIndex - 1].order) / 2;\n      sortList();\n      return;\n    }\n\n    list[sourceIndex].order =\n      (list[destinationIndex].order + list[destinationIndex + 1].order) / 2;\n    sortList();\n  };\n\n  return (\n    <Box>\n      <InputHeader\n        title={\n          title ||\n          t('toolMultiplePdfInput.inputTitle', {\n            type: type.charAt(0).toUpperCase() + type.slice(1)\n          })\n        }\n      />\n      <Box\n        sx={{\n          width: '100%',\n          height: '300px',\n\n          border: value?.length ? 0 : 1,\n          borderRadius: 2,\n          boxShadow: '5',\n          bgcolor: 'background.paper',\n          position: 'relative'\n        }}\n      >\n        <Box\n          width=\"100%\"\n          height=\"100%\"\n          sx={{\n            overflow: 'auto',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center',\n            flexWrap: 'wrap',\n            position: 'relative'\n          }}\n        >\n          {value?.length ? (\n            value.map((file, index) => (\n              <Box\n                key={index}\n                sx={{\n                  margin: 1,\n                  display: 'flex',\n                  alignItems: 'center',\n                  justifyContent: 'space-between',\n                  width: '200px',\n                  border: 1,\n                  borderRadius: 1,\n                  padding: 1\n                }}\n              >\n                <Box sx={{ display: 'flex', alignItems: 'center' }}>\n                  <PictureAsPdfIcon />\n                  <Typography sx={{ marginLeft: 1 }}>\n                    {fileNameTruncate(file.file.name)}\n                  </Typography>\n                </Box>\n                <Box\n                  sx={{ cursor: 'pointer' }}\n                  onClick={() => {\n                    const updatedFiles = value.filter((_, i) => i !== index);\n                    onChange(updatedFiles);\n                  }}\n                >\n                  ✖\n                </Box>\n              </Box>\n            ))\n          ) : (\n            <Typography variant=\"body2\" color=\"text.secondary\">\n              {t('toolMultiplePdfInput.noFilesSelected')}\n            </Typography>\n          )}\n        </Box>\n      </Box>\n\n      <InputFooter handleImport={handleImportClick} handleClear={handleClear} />\n      <input\n        ref={fileInputRef}\n        style={{ display: 'none' }}\n        type=\"file\"\n        accept={accept.join(',')}\n        onChange={handleFileChange}\n        multiple={true}\n      />\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/input/ToolMultipleVideoInput.tsx",
    "content": "import { ReactNode, useContext, useEffect, useRef, useState } from 'react';\nimport { Box, useTheme } from '@mui/material';\nimport Typography from '@mui/material/Typography';\nimport InputHeader from '../InputHeader';\nimport InputFooter from './InputFooter';\nimport { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';\nimport { isArray } from 'lodash';\nimport VideoFileIcon from '@mui/icons-material/VideoFile';\n\ninterface MultiVideoInputComponentProps {\n  accept: string[];\n  title?: string;\n  type: 'video';\n  value: MultiVideoInput[];\n  onChange: (file: MultiVideoInput[]) => void;\n}\n\nexport interface MultiVideoInput {\n  file: File;\n  order: number;\n}\n\nexport default function ToolMultipleVideoInput({\n  value,\n  onChange,\n  accept,\n  title,\n  type\n}: MultiVideoInputComponentProps) {\n  console.log('ToolMultipleVideoInput rendering with value:', value);\n\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    const files = event.target.files;\n    console.log('File change event:', files);\n    if (files)\n      onChange([\n        ...value,\n        ...Array.from(files).map((file) => ({ file, order: value.length }))\n      ]);\n  };\n\n  const handleImportClick = () => {\n    console.log('Import clicked');\n    fileInputRef.current?.click();\n  };\n\n  function handleClear() {\n    console.log('Clear clicked');\n    onChange([]);\n  }\n\n  function fileNameTruncate(fileName: string) {\n    const maxLength = 15;\n    if (fileName.length > maxLength) {\n      return fileName.slice(0, maxLength) + '...';\n    }\n    return fileName;\n  }\n\n  const sortList = () => {\n    const list = [...value];\n    list.sort((a, b) => a.order - b.order);\n    onChange(list);\n  };\n\n  const reorderList = (sourceIndex: number, destinationIndex: number) => {\n    if (destinationIndex === sourceIndex) {\n      return;\n    }\n    const list = [...value];\n\n    if (destinationIndex === 0) {\n      list[sourceIndex].order = list[0].order - 1;\n      sortList();\n      return;\n    }\n\n    if (destinationIndex === list.length - 1) {\n      list[sourceIndex].order = list[list.length - 1].order + 1;\n      sortList();\n      return;\n    }\n\n    if (destinationIndex < sourceIndex) {\n      list[sourceIndex].order =\n        (list[destinationIndex].order + list[destinationIndex - 1].order) / 2;\n      sortList();\n      return;\n    }\n\n    list[sourceIndex].order =\n      (list[destinationIndex].order + list[destinationIndex + 1].order) / 2;\n    sortList();\n  };\n\n  return (\n    <Box>\n      <InputHeader\n        title={title || 'Input ' + type.charAt(0).toUpperCase() + type.slice(1)}\n      />\n      <Box\n        sx={{\n          width: '100%',\n          height: '300px',\n          border: value?.length ? 0 : 1,\n          borderRadius: 2,\n          boxShadow: '5',\n          bgcolor: 'background.paper',\n          position: 'relative'\n        }}\n      >\n        <Box\n          width=\"100%\"\n          height=\"100%\"\n          sx={{\n            overflow: 'auto',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center',\n            flexWrap: 'wrap',\n            position: 'relative'\n          }}\n        >\n          {value?.length ? (\n            value.map((file, index) => (\n              <Box\n                key={index}\n                sx={{\n                  margin: 1,\n                  display: 'flex',\n                  alignItems: 'center',\n                  justifyContent: 'space-between',\n                  width: '200px',\n                  border: 1,\n                  borderRadius: 1,\n                  padding: 1\n                }}\n              >\n                <Box sx={{ display: 'flex', alignItems: 'center' }}>\n                  <VideoFileIcon />\n                  <Typography sx={{ marginLeft: 1 }}>\n                    {fileNameTruncate(file.file.name)}\n                  </Typography>\n                </Box>\n                <Box\n                  sx={{ cursor: 'pointer' }}\n                  onClick={() => {\n                    const updatedFiles = value.filter((_, i) => i !== index);\n                    onChange(updatedFiles);\n                  }}\n                >\n                  ✖\n                </Box>\n              </Box>\n            ))\n          ) : (\n            <Typography variant=\"body2\" color=\"text.secondary\">\n              No files selected\n            </Typography>\n          )}\n        </Box>\n      </Box>\n\n      <InputFooter handleImport={handleImportClick} handleClear={handleClear} />\n      <input\n        ref={fileInputRef}\n        style={{ display: 'none' }}\n        type=\"file\"\n        accept={accept.join(',')}\n        onChange={handleFileChange}\n        multiple={true}\n      />\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/input/ToolPdfInput.tsx",
    "content": "import React, { useRef } from 'react';\nimport BaseFileInput from './BaseFileInput';\nimport { BaseFileInputProps } from './file-input-utils';\n\ninterface PdfFileInputProps extends BaseFileInputProps {}\n\nexport default function ToolPdfInput({ ...props }: PdfFileInputProps) {\n  const pdfRef = useRef<HTMLIFrameElement>(null);\n\n  return (\n    <BaseFileInput {...props} type={'pdf'}>\n      {({ preview }) => (\n        <iframe\n          ref={pdfRef}\n          src={preview}\n          width=\"100%\"\n          height=\"100%\"\n          style={{ maxWidth: '500px' }}\n        />\n      )}\n    </BaseFileInput>\n  );\n}\n"
  },
  {
    "path": "src/components/input/ToolTextInput.tsx",
    "content": "import { Box, TextField } from '@mui/material';\nimport React, { useContext, useRef } from 'react';\nimport { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';\nimport InputHeader from '../InputHeader';\nimport InputFooter from './InputFooter';\nimport { useTranslation } from 'react-i18next';\n\nexport default function ToolTextInput({\n  value,\n  onChange,\n  title = 'Input text',\n  placeholder\n}: {\n  title?: string;\n  value: string;\n  onChange: (value: string) => void;\n  placeholder?: string;\n}) {\n  const { t } = useTranslation();\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const handleCopy = () => {\n    navigator.clipboard\n      .writeText(value)\n      .then(() => showSnackBar(t('toolTextInput.copied'), 'success'))\n      .catch((err) => {\n        showSnackBar(t('toolTextInput.copyFailed', { error: err }), 'error');\n      });\n  };\n  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    const file = event.target.files?.[0];\n    if (file) {\n      const reader = new FileReader();\n      reader.onload = (e) => {\n        const text = e.target?.result;\n        if (typeof text === 'string') {\n          onChange(text);\n        }\n      };\n      reader.readAsText(file);\n    }\n  };\n\n  const handleImportClick = () => {\n    fileInputRef.current?.click();\n  };\n  return (\n    <Box>\n      <InputHeader title={title || t('toolTextInput.input')} />\n      <TextField\n        value={value}\n        onChange={(event) => onChange(event.target.value)}\n        fullWidth\n        multiline\n        rows={10}\n        placeholder={placeholder || t('toolTextInput.placeholder')}\n        sx={{\n          '&.MuiTextField-root': {\n            backgroundColor: 'background.paper'\n          }\n        }}\n        inputProps={{\n          'data-testid': 'text-input'\n        }}\n      />\n      <InputFooter handleCopy={handleCopy} handleImport={handleImportClick} />\n      <input\n        type=\"file\"\n        accept=\"*\"\n        ref={fileInputRef}\n        style={{ display: 'none' }}\n        onChange={handleFileChange}\n      />\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/input/ToolVideoInput.tsx",
    "content": "import React, { useRef, useState } from 'react';\nimport { Box, Typography } from '@mui/material';\nimport Slider from 'rc-slider';\nimport 'rc-slider/assets/index.css';\nimport BaseFileInput from './BaseFileInput';\nimport { BaseFileInputProps, formatTime } from './file-input-utils';\n\ninterface VideoFileInputProps extends Omit<BaseFileInputProps, 'accept'> {\n  showTrimControls?: boolean;\n  onTrimChange?: (trimStart: number, trimEnd: number) => void;\n  trimStart?: number;\n  trimEnd?: number;\n  accept?: string[];\n}\n\nexport default function ToolVideoInput({\n  showTrimControls = false,\n  onTrimChange,\n  trimStart = 0,\n  trimEnd = 100,\n  accept = ['video/*', '.mkv'],\n  ...props\n}: VideoFileInputProps) {\n  const videoRef = useRef<HTMLVideoElement>(null);\n  const [videoDuration, setVideoDuration] = useState(0);\n\n  const onVideoLoad = (e: React.SyntheticEvent<HTMLVideoElement>) => {\n    const duration = e.currentTarget.duration;\n    setVideoDuration(duration);\n\n    if (onTrimChange && trimStart === 0 && trimEnd === 100) {\n      onTrimChange(0, duration);\n    }\n  };\n\n  const handleTrimChange = (start: number, end: number) => {\n    if (onTrimChange) {\n      onTrimChange(start, end);\n    }\n  };\n\n  return (\n    <BaseFileInput {...props} type={'video'} accept={accept}>\n      {({ preview }) => (\n        <Box\n          sx={{\n            position: 'relative',\n            width: '100%',\n            height: '100%',\n            display: 'flex',\n            flexDirection: 'column',\n            alignItems: 'center',\n            justifyContent: 'center'\n          }}\n        >\n          <video\n            ref={videoRef}\n            src={preview}\n            style={{\n              maxWidth: '100%',\n              maxHeight: showTrimControls ? 'calc(100% - 50px)' : '100%'\n            }}\n            onLoadedMetadata={onVideoLoad}\n            controls={!showTrimControls}\n          />\n\n          {showTrimControls && videoDuration > 0 && (\n            <Box\n              sx={{\n                width: '100%',\n                padding: '10px 20px',\n                position: 'absolute',\n                bottom: 0,\n                left: 0,\n                backgroundColor: 'rgba(0,0,0,0.5)',\n                color: 'white',\n                display: 'flex',\n                flexDirection: 'column',\n                gap: 1\n              }}\n            >\n              <Box\n                sx={{\n                  display: 'flex',\n                  justifyContent: 'space-between',\n                  alignItems: 'center'\n                }}\n              >\n                <Typography variant=\"caption\">\n                  Start: {formatTime(trimStart || 0)}\n                </Typography>\n                <Typography variant=\"caption\">\n                  End: {formatTime(trimEnd || videoDuration)}\n                </Typography>\n              </Box>\n              <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>\n                <div\n                  className=\"range-slider-container\"\n                  style={{ margin: '20px 0', width: '100%' }}\n                >\n                  <Slider\n                    range\n                    min={0}\n                    max={videoDuration}\n                    step={0.1}\n                    value={[trimStart || 0, trimEnd || videoDuration]}\n                    onChange={(values) => {\n                      if (Array.isArray(values)) {\n                        handleTrimChange(values[0], values[1]);\n                      }\n                    }}\n                    allowCross={false}\n                    pushable={0.1}\n                  />\n                </div>\n              </Box>\n            </Box>\n          )}\n        </Box>\n      )}\n    </BaseFileInput>\n  );\n}\n"
  },
  {
    "path": "src/components/input/file-input-utils.ts",
    "content": "export interface BaseFileInputProps {\n  value: File | null;\n  onChange: (file: File | null) => void;\n  accept: string[];\n  title?: string;\n}\n\nexport const formatTime = (seconds: number): string => {\n  const minutes = Math.floor(seconds / 60);\n  const remainingSeconds = Math.floor(seconds % 60);\n  return `${minutes.toString().padStart(2, '0')}:${remainingSeconds\n    .toString()\n    .padStart(2, '0')}`;\n};\n\nexport const createObjectURL = (file: File): string => {\n  return URL.createObjectURL(file);\n};\n\nexport const revokeObjectURL = (url: string): void => {\n  URL.revokeObjectURL(url);\n};\n"
  },
  {
    "path": "src/components/options/CheckboxWithDesc.tsx",
    "content": "import React from 'react';\nimport { Box, Checkbox, FormControlLabel, Typography } from '@mui/material';\n\nconst CheckboxWithDesc = ({\n  title,\n  description,\n  checked,\n  onChange,\n  disabled\n}: {\n  title: string;\n  description?: string;\n  checked: boolean;\n  onChange: (value: boolean) => void;\n  disabled?: boolean;\n}) => {\n  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    onChange(event.target.checked);\n  };\n\n  return (\n    <Box mb={3}>\n      <FormControlLabel\n        control={\n          <Checkbox\n            checked={checked}\n            onChange={handleChange}\n            disabled={disabled}\n          />\n        }\n        label={title}\n      />\n      {description && (\n        <Typography fontSize={12} mt={1}>\n          {description}\n        </Typography>\n      )}\n    </Box>\n  );\n};\n\nexport default CheckboxWithDesc;\n"
  },
  {
    "path": "src/components/options/ColorSelector.tsx",
    "content": "import React, { ChangeEvent, useRef, useState } from 'react';\nimport { Box, Stack, TextField, TextFieldProps } from '@mui/material';\nimport PaletteIcon from '@mui/icons-material/Palette';\nimport IconButton from '@mui/material/IconButton';\nimport Typography from '@mui/material/Typography';\nimport { globalDescriptionFontSize } from '../../config/uiConfig';\n\ninterface ColorSelectorProps {\n  value: string;\n  onColorChange: (val: string) => void;\n  description: string;\n}\n\nconst ColorSelector: React.FC<ColorSelectorProps & TextFieldProps> = ({\n  value = '#ffffff',\n  onColorChange,\n  description,\n  ...props\n}) => {\n  const [color, setColor] = useState<string>(value);\n  const inputRef = useRef<HTMLInputElement>(null);\n\n  const handleColorChange = (event: ChangeEvent<HTMLInputElement>) => {\n    const val = event.target.value;\n    setColor(val);\n    onColorChange(val);\n  };\n\n  return (\n    <Box mb={3}>\n      <Stack direction={'row'}>\n        <TextField\n          sx={{ backgroundColor: 'background.paper' }}\n          value={color}\n          onChange={handleColorChange}\n          {...props}\n        />\n        <IconButton onClick={() => inputRef.current?.click()}>\n          <PaletteIcon />\n        </IconButton>\n        <TextField\n          style={{ visibility: 'hidden' }}\n          inputRef={inputRef}\n          type=\"color\"\n          value={color}\n          onChange={handleColorChange}\n        />\n      </Stack>\n      <Typography fontSize={globalDescriptionFontSize} mt={1}>\n        {description}\n      </Typography>\n    </Box>\n  );\n};\n\nexport default ColorSelector;\n"
  },
  {
    "path": "src/components/options/RadioWithTextField.tsx",
    "content": "import { Box } from '@mui/material';\nimport React from 'react';\nimport TextFieldWithDesc from './TextFieldWithDesc';\nimport SimpleRadio from './SimpleRadio';\n\nconst RadioWithTextField = ({\n  title,\n  onRadioClick,\n  checked,\n  value,\n  description,\n  onTextChange,\n  radioDescription\n}: {\n  fieldName: string;\n  title: string;\n  checked: boolean;\n  onRadioClick: () => void;\n  value: string;\n  description: string;\n  onTextChange: (value: string) => void;\n  radioDescription?: string;\n}) => {\n  return (\n    <Box mb={3}>\n      <SimpleRadio\n        checked={checked}\n        onClick={onRadioClick}\n        title={title}\n        description={radioDescription}\n      />\n      <TextFieldWithDesc\n        value={value}\n        onOwnChange={onTextChange}\n        description={description}\n      />\n    </Box>\n  );\n};\nexport default RadioWithTextField;\n"
  },
  {
    "path": "src/components/options/SelectWithDesc.tsx",
    "content": "import React from 'react';\nimport {\n  Box,\n  MenuItem,\n  Select,\n  SelectChangeEvent,\n  Typography\n} from '@mui/material';\n\ninterface Option<T extends string | boolean> {\n  label: string;\n  value: T;\n}\n\nconst SelectWithDesc = <T extends string | boolean>({\n  selected,\n  options,\n  onChange,\n  description\n}: {\n  selected: T;\n  options: Option<T>[];\n  onChange: (value: T) => void;\n  description: string;\n}) => {\n  const handleChange = (event: SelectChangeEvent<T>) => {\n    const newValue =\n      typeof selected === 'boolean'\n        ? event.target.value === 'true'\n        : event.target.value;\n    onChange(newValue as T);\n  };\n\n  return (\n    <Box mb={3}>\n      <Select value={selected} onChange={handleChange}>\n        {options.map((option) => (\n          <MenuItem key={option.label} value={option.value.toString()}>\n            {option.label}\n          </MenuItem>\n        ))}\n      </Select>\n      <Typography fontSize={12} mt={1}>\n        {description}\n      </Typography>\n    </Box>\n  );\n};\n\nexport default SelectWithDesc;\n"
  },
  {
    "path": "src/components/options/SimpleRadio.tsx",
    "content": "import { Box, Radio, Stack } from '@mui/material';\nimport { Field, useFormikContext } from 'formik';\nimport Typography from '@mui/material/Typography';\nimport React from 'react';\nimport { globalDescriptionFontSize } from '../../config/uiConfig';\n\ninterface SimpleRadioProps {\n  title: string;\n  description?: string;\n  checked: boolean;\n  onClick: () => void;\n}\n\nconst SimpleRadio: React.FC<SimpleRadioProps> = ({\n  onClick,\n  title,\n  description,\n  checked\n}) => {\n  return (\n    <Box>\n      <Stack\n        direction={'row'}\n        sx={{ mt: 2, mb: 1, cursor: 'pointer' }}\n        alignItems={'center'}\n        onClick={onClick}\n      >\n        <Radio checked={checked} onClick={onClick} />\n        <Typography>{title}</Typography>\n      </Stack>\n      {description && (\n        <Typography ml={2} fontSize={globalDescriptionFontSize}>\n          {description}\n        </Typography>\n      )}\n    </Box>\n  );\n};\n\nexport default SimpleRadio;\n"
  },
  {
    "path": "src/components/options/TextFieldWithDesc.tsx",
    "content": "import { Box, TextField, TextFieldProps } from '@mui/material';\nimport Typography from '@mui/material/Typography';\nimport React from 'react';\n\ntype OwnProps = {\n  description?: string;\n  value: string | number;\n  onOwnChange: (value: string) => void;\n  placeholder?: string;\n};\nconst TextFieldWithDesc = ({\n  description,\n  value,\n  onOwnChange,\n  placeholder,\n  ...props\n}: TextFieldProps & OwnProps) => {\n  return (\n    <Box mb={3}>\n      <TextField\n        placeholder={placeholder}\n        sx={{ backgroundColor: 'background.paper' }}\n        value={value}\n        onChange={(event) => onOwnChange(event.target.value)}\n        {...props}\n      />\n      {description && (\n        <Typography fontSize={12} mt={1}>\n          {description}\n        </Typography>\n      )}\n    </Box>\n  );\n};\n\nexport default TextFieldWithDesc;\n"
  },
  {
    "path": "src/components/options/ToolOptionGroups.tsx",
    "content": "import Typography from '@mui/material/Typography';\nimport React, { ReactNode } from 'react';\nimport Grid from '@mui/material/Grid';\n\nexport interface ToolOptionGroup {\n  title: string;\n  component: ReactNode;\n}\n\nexport default function ToolOptionGroups({\n  groups,\n  vertical\n}: {\n  groups: ToolOptionGroup[];\n  vertical?: boolean;\n}) {\n  return (\n    <Grid container spacing={2}>\n      {groups.map((group) => (\n        <Grid item xs={12} md={vertical ? 12 : 4} key={group.title}>\n          <Typography mb={1} fontSize={22}>\n            {group.title}\n          </Typography>\n          {group.component}\n        </Grid>\n      ))}\n    </Grid>\n  );\n}\n"
  },
  {
    "path": "src/components/options/ToolOptions.tsx",
    "content": "import { Box, Stack, useTheme } from '@mui/material';\nimport SettingsIcon from '@mui/icons-material/Settings';\nimport Typography from '@mui/material/Typography';\nimport React, { ReactNode } from 'react';\nimport { FormikProps, FormikValues, useFormikContext } from 'formik';\nimport ToolOptionGroups, { ToolOptionGroup } from './ToolOptionGroups';\nimport { useTranslation } from 'react-i18next';\n\nexport type UpdateField<T> = <Y extends keyof T>(field: Y, value: T[Y]) => void;\ntype NonEmptyArray<T> = [T, ...T[]];\nexport type GetGroupsType<T> = (\n  formikProps: FormikProps<T> & { updateField: UpdateField<T> }\n) => NonEmptyArray<ToolOptionGroup>;\n\nexport default function ToolOptions<T extends FormikValues>({\n  children,\n  getGroups,\n  vertical\n}: {\n  children?: ReactNode;\n  getGroups: GetGroupsType<T> | null;\n  vertical?: boolean;\n}) {\n  const { t } = useTranslation();\n  const theme = useTheme();\n  const formikContext = useFormikContext<T>();\n\n  // Early return if no groups to display\n  if (!getGroups) {\n    return null;\n  }\n\n  const updateField: UpdateField<T> = (field, value) => {\n    formikContext.setFieldValue(field as string, value);\n  };\n\n  return (\n    <Box\n      sx={{\n        mb: 2,\n        borderRadius: 2,\n        padding: 2,\n        backgroundColor: 'background.lightSecondary',\n        boxShadow: '2'\n      }}\n      mt={2}\n    >\n      <Stack direction={'row'} spacing={1} alignItems={'center'}>\n        <SettingsIcon />\n        <Typography fontSize={22}>{t('toolOptions.title')}</Typography>\n      </Stack>\n      <Box mt={2}>\n        <Stack direction={'row'} spacing={2}>\n          <ToolOptionGroups\n            groups={getGroups({ ...formikContext, updateField }) ?? null}\n            vertical={vertical}\n          />\n          {children}\n        </Stack>\n      </Box>\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/result/ResultFooter.tsx",
    "content": "import { Stack } from '@mui/material';\nimport Button from '@mui/material/Button';\nimport DownloadIcon from '@mui/icons-material/Download';\nimport ContentPasteIcon from '@mui/icons-material/ContentPaste';\nimport React from 'react';\nimport { useTranslation } from 'react-i18next';\n\nexport default function ResultFooter({\n  handleDownload,\n  handleCopy,\n  disabled,\n  hideCopy,\n  downloadLabel\n}: {\n  handleDownload: () => void;\n  handleCopy?: () => void;\n  disabled?: boolean;\n  hideCopy?: boolean;\n  downloadLabel?: string;\n}) {\n  const { t } = useTranslation();\n  return (\n    <Stack mt={1} direction={'row'} spacing={2}>\n      <Button\n        disabled={disabled}\n        onClick={handleDownload}\n        startIcon={<DownloadIcon />}\n      >\n        {downloadLabel || t('resultFooter.download')}\n      </Button>\n      {!hideCopy && (\n        <Button\n          disabled={disabled}\n          onClick={handleCopy}\n          startIcon={<ContentPasteIcon />}\n        >\n          {t('resultFooter.copy')}\n        </Button>\n      )}\n    </Stack>\n  );\n}\n"
  },
  {
    "path": "src/components/result/ToolFileResult.tsx",
    "content": "import { Box, CircularProgress, Typography, useTheme } from '@mui/material';\nimport React, { useContext } from 'react';\nimport InputHeader from '../InputHeader';\nimport greyPattern from '@assets/grey-pattern.png';\nimport { globalInputHeight } from '../../config/uiConfig';\nimport ResultFooter from './ResultFooter';\nimport { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';\nimport { useTranslation } from 'react-i18next';\n\nexport default function ToolFileResult({\n  title = 'Result',\n  value,\n  extension,\n  loading,\n  loadingText\n}: {\n  title?: string;\n  value: File | null;\n  extension?: string;\n  loading?: boolean;\n  loadingText?: string;\n}) {\n  const { t } = useTranslation();\n  const [preview, setPreview] = React.useState<string | null>(null);\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n  const theme = useTheme();\n\n  React.useEffect(() => {\n    if (value) {\n      const objectUrl = URL.createObjectURL(value);\n      setPreview(objectUrl);\n\n      return () => URL.revokeObjectURL(objectUrl);\n    } else {\n      setPreview(null);\n    }\n  }, [value]);\n\n  const handleCopy = () => {\n    if (value) {\n      const blob = new Blob([value], { type: value.type });\n      const clipboardItem = new ClipboardItem({ [value.type]: blob });\n\n      navigator.clipboard\n        .write([clipboardItem])\n        .then(() => showSnackBar(t('toolFileResult.copied'), 'success'))\n        .catch((err) => {\n          showSnackBar(t('toolFileResult.copyFailed', { error: err }), 'error');\n        });\n    }\n  };\n\n  const handleDownload = () => {\n    if (value) {\n      let filename: string = value.name;\n      if (extension) {\n        // Split at the last period to separate filename and extension\n        const parts = filename.split('.');\n        // If there's more than one part (meaning there was a period)\n        if (parts.length > 1) {\n          // Remove the last part (the extension) and add the new extension\n          parts.pop();\n          filename = `${parts.join('.')}.${extension}`;\n        } else {\n          // No extension exists, just add it\n          filename = `${filename}.${extension}`;\n        }\n      }\n      const blob = new Blob([value], { type: value.type });\n      const url = window.URL.createObjectURL(blob);\n      const a = document.createElement('a');\n      a.href = url;\n      a.download = filename;\n      document.body.appendChild(a);\n      a.click();\n      document.body.removeChild(a);\n      window.URL.revokeObjectURL(url);\n    }\n  };\n\n  type SupportedFileType = 'image' | 'video' | 'audio' | 'pdf' | 'unknown';\n  // Determine the file type based on MIME type\n  const getFileType = (): SupportedFileType => {\n    if (!value) return 'unknown';\n    if (value.type.startsWith('image/')) return 'image';\n    if (value.type.startsWith('video/')) return 'video';\n    if (value.type.startsWith('audio/')) return 'audio';\n    if (value.type.startsWith('application/pdf')) return 'pdf';\n    return 'unknown';\n  };\n\n  const fileType = getFileType();\n\n  return (\n    <Box>\n      <InputHeader title={title || t('toolFileResult.result')} />\n      <Box\n        sx={{\n          width: '100%',\n          height: globalInputHeight,\n          border: preview ? 0 : 1,\n          borderRadius: 2,\n          boxShadow: '5',\n          bgcolor: 'background.paper'\n        }}\n      >\n        {loading ? (\n          <Box\n            sx={{\n              display: 'flex',\n              flexDirection: 'column',\n              alignItems: 'center',\n              justifyContent: 'center',\n              height: '100%'\n            }}\n          >\n            <CircularProgress />\n            <Typography variant=\"body2\" sx={{ mt: 2 }}>\n              {loadingText || t('toolFileResult.loading')}\n            </Typography>\n          </Box>\n        ) : (\n          preview && (\n            <Box\n              width={'100%'}\n              height={'100%'}\n              sx={{\n                display: 'flex',\n                alignItems: 'center',\n                justifyContent: 'center',\n                backgroundImage:\n                  theme.palette.mode === 'dark' ? null : `url(${greyPattern})`\n              }}\n            >\n              {fileType === 'image' && (\n                <img\n                  src={preview}\n                  alt=\"Result\"\n                  style={{ maxWidth: '100%', maxHeight: globalInputHeight }}\n                />\n              )}\n              {fileType === 'video' && (\n                <video\n                  src={preview}\n                  controls\n                  style={{ maxWidth: '100%', maxHeight: globalInputHeight }}\n                />\n              )}\n              {fileType === 'audio' && (\n                <audio\n                  src={preview}\n                  controls\n                  style={{ width: '100%', maxWidth: '500px' }}\n                />\n              )}\n              {fileType === 'pdf' && (\n                <iframe\n                  src={preview}\n                  width=\"100%\"\n                  height=\"100%\"\n                  style={{ maxWidth: '500px' }}\n                />\n              )}\n              {fileType === 'unknown' && (\n                <Box sx={{ padding: 2, textAlign: 'center' }}>\n                  File processed successfully. Click download to save the\n                  result.\n                </Box>\n              )}\n            </Box>\n          )\n        )}\n      </Box>\n      <ResultFooter\n        disabled={!value}\n        handleCopy={handleCopy}\n        handleDownload={handleDownload}\n        hideCopy={\n          fileType === 'video' || fileType === 'audio' || fileType === 'pdf'\n        }\n      />\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/result/ToolMultiFileResult.tsx",
    "content": "import {\n  Box,\n  CircularProgress,\n  Typography,\n  useTheme,\n  Button\n} from '@mui/material';\nimport InputHeader from '../InputHeader';\nimport greyPattern from '@assets/grey-pattern.png';\nimport { globalInputHeight } from '../../config/uiConfig';\nimport ResultFooter from './ResultFooter';\nimport { useTranslation } from 'react-i18next';\nimport React, { useContext } from 'react';\nimport { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';\n\nexport default function ToolMultiFileResult({\n  title = 'Result',\n  value,\n  zipFile,\n  loading,\n  loadingText\n}: {\n  title?: string;\n  value: File[];\n  zipFile?: File | null;\n  loading?: boolean;\n  loadingText?: string;\n}) {\n  const { t } = useTranslation();\n  const theme = useTheme();\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n\n  const getFileType = (\n    file: File\n  ): 'image' | 'video' | 'audio' | 'pdf' | 'unknown' => {\n    if (file.type.startsWith('image/')) return 'image';\n    if (file.type.startsWith('video/')) return 'video';\n    if (file.type.startsWith('audio/')) return 'audio';\n    if (file.type.startsWith('application/pdf')) return 'pdf';\n    return 'unknown';\n  };\n\n  const handleDownload = (file: File) => {\n    const url = URL.createObjectURL(file);\n    const a = document.createElement('a');\n    a.href = url;\n    a.download = file.name;\n    document.body.appendChild(a);\n    a.click();\n    document.body.removeChild(a);\n    URL.revokeObjectURL(url);\n  };\n\n  const handleCopy = () => {\n    if (zipFile) {\n      const blob = new Blob([zipFile], { type: zipFile.type });\n      const clipboardItem = new ClipboardItem({ [zipFile.type]: blob });\n      navigator.clipboard\n        .write([clipboardItem])\n        .then(() => showSnackBar(t('toolMultiFileResult.copied'), 'success'))\n        .catch((err) => {\n          showSnackBar(\n            t('toolMultiFileResult.copyFailed', { error: err }),\n            'error'\n          );\n        });\n    }\n  };\n\n  return (\n    <Box>\n      <InputHeader title={title || t('toolMultiFileResult.result')} />\n      <Box\n        sx={{\n          width: '100%',\n          height: globalInputHeight,\n          overflowY: 'auto',\n          display: 'flex',\n          flexDirection: 'column',\n          gap: 2,\n          border: 1,\n          borderRadius: 2,\n          boxShadow: '5',\n          bgcolor: 'background.paper',\n          alignItems: 'center',\n          p: 2\n        }}\n      >\n        {loading ? (\n          <Box\n            sx={{\n              display: 'flex',\n              flexDirection: 'column',\n              alignItems: 'center',\n              justifyContent: 'center',\n              height: globalInputHeight\n            }}\n          >\n            <CircularProgress />\n            <Typography variant=\"body2\" sx={{ mt: 2 }}>\n              {loadingText || t('toolMultiFileResult.loading')}\n            </Typography>\n          </Box>\n        ) : (\n          value.length > 0 &&\n          value.map((file, idx) => {\n            const preview = URL.createObjectURL(file);\n            const fileType = getFileType(file);\n\n            return (\n              <Box\n                key={idx}\n                sx={{\n                  backgroundImage:\n                    fileType === 'image' && theme.palette.mode !== 'dark'\n                      ? `url(${greyPattern})`\n                      : 'none',\n                  p: 1,\n                  border: '1px solid #ddd',\n                  borderRadius: 2,\n                  display: 'flex',\n                  flexDirection: 'column',\n                  alignItems: 'center'\n                }}\n              >\n                {fileType === 'image' && (\n                  <img\n                    src={preview}\n                    alt={`Preview ${idx}`}\n                    style={{ maxWidth: '100%', maxHeight: 300 }}\n                  />\n                )}\n                {fileType === 'video' && (\n                  <video src={preview} controls style={{ maxWidth: '100%' }} />\n                )}\n                {fileType === 'audio' && (\n                  <audio src={preview} controls style={{ width: '100%' }} />\n                )}\n                {fileType === 'pdf' && (\n                  <iframe src={preview} width=\"100%\" height=\"400px\" />\n                )}\n                {fileType === 'unknown' && (\n                  <Typography>File ready. Click below to download.</Typography>\n                )}\n                <Button\n                  onClick={() => handleDownload(file)}\n                  size=\"small\"\n                  sx={{ mt: 1 }}\n                  variant=\"contained\"\n                >\n                  Download {file.name}\n                </Button>\n              </Box>\n            );\n          })\n        )}\n      </Box>\n      <ResultFooter\n        downloadLabel={'Download All as ZIP'}\n        hideCopy\n        disabled={!zipFile}\n        handleDownload={() => zipFile && handleDownload(zipFile)}\n      />\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/components/result/ToolTextResult.tsx",
    "content": "import { Box, CircularProgress, TextField, Typography } from '@mui/material';\nimport React, { useContext } from 'react';\nimport { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';\nimport InputHeader from '../InputHeader';\nimport ResultFooter from './ResultFooter';\nimport { replaceSpecialCharacters } from '@utils/string';\nimport mime from 'mime';\nimport { globalInputHeight } from '../../config/uiConfig';\nimport { useTranslation } from 'react-i18next';\n\nexport default function ToolTextResult({\n  title = 'Result',\n  value,\n  extension = 'txt',\n  keepSpecialCharacters,\n  loading\n}: {\n  title?: string;\n  value: string;\n  extension?: string;\n  keepSpecialCharacters?: boolean;\n  loading?: boolean;\n}) {\n  const { t } = useTranslation();\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n  const handleCopy = () => {\n    navigator.clipboard\n      .writeText(value)\n      .then(() => showSnackBar(t('toolTextResult.copied'), 'success'))\n      .catch((err) => {\n        showSnackBar(t('toolTextResult.copyFailed', { error: err }), 'error');\n      });\n  };\n  const handleDownload = () => {\n    const filename = `output-omni-tools.${extension}`;\n\n    const mimeType = mime.getType(extension) || 'text/plain';\n\n    const blob = new Blob([value], {\n      type: mimeType\n    });\n    const url = window.URL.createObjectURL(blob);\n    const a = document.createElement('a');\n    a.href = url;\n    a.download = filename;\n    document.body.appendChild(a);\n    a.click();\n    document.body.removeChild(a);\n    window.URL.revokeObjectURL(url);\n  };\n  return (\n    <Box>\n      <InputHeader title={title || t('toolTextResult.result')} />\n      {loading ? (\n        <Box\n          sx={{\n            display: 'flex',\n            flexDirection: 'column',\n            alignItems: 'center',\n            justifyContent: 'center',\n            height: globalInputHeight\n          }}\n        >\n          <CircularProgress />\n          <Typography variant=\"body2\" sx={{ mt: 2 }}>\n            {t('toolTextResult.loading')}\n          </Typography>\n        </Box>\n      ) : (\n        <TextField\n          value={\n            keepSpecialCharacters ? value : replaceSpecialCharacters(value)\n          }\n          fullWidth\n          multiline\n          sx={{\n            '&.MuiTextField-root': {\n              backgroundColor: 'background.paper'\n            }\n          }}\n          rows={10}\n          inputProps={{ 'data-testid': 'text-result' }}\n        />\n      )}\n      <ResultFooter handleCopy={handleCopy} handleDownload={handleDownload} />\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/config/muiConfig.ts",
    "content": "import { createTheme, ThemeOptions } from '@mui/material';\n\nconst sharedThemeOptions: ThemeOptions = {\n  typography: {\n    button: {\n      textTransform: 'none'\n    }\n  },\n  zIndex: { snackbar: 100000 }\n};\nexport const lightTheme = createTheme({\n  ...sharedThemeOptions,\n  palette: {\n    background: {\n      default: '#F5F5FA',\n      hover: '#FAFAFD',\n      lightSecondary: '#EBF5FF',\n      darkSecondary: '#5581b5'\n    }\n  },\n  components: {\n    MuiButton: {\n      styleOverrides: {\n        contained: { color: '#ffffff', backgroundColor: '#1976d2' }\n      }\n    }\n  }\n});\n\nexport const darkTheme = createTheme({\n  ...sharedThemeOptions,\n  palette: {\n    mode: 'dark',\n    background: {\n      default: '#1C1F20',\n      paper: '#181a1b',\n      hover: '#1a1c1d',\n      lightSecondary: '#1E2021',\n      darkSecondary: '#3C5F8A'\n    },\n    text: { primary: '#ffffff' }\n  },\n  components: {\n    MuiButton: {\n      styleOverrides: {\n        contained: { color: '#ffffff', backgroundColor: '#145ea8' }\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "src/config/routesConfig.tsx",
    "content": "import { Navigate, RouteObject } from 'react-router-dom';\nimport { lazy } from 'react';\n\nconst Home = lazy(() => import('../pages/home'));\nconst ToolsByCategory = lazy(() => import('../pages/tools-by-category'));\n\nconst routes: RouteObject[] = [\n  {\n    path: '/',\n    element: <Home />\n  },\n  {\n    path: '/categories/:categoryName',\n    element: <ToolsByCategory />\n  },\n  {\n    path: '*',\n    element: <Navigate to=\"404\" />\n  }\n];\n\nexport default routes;\n"
  },
  {
    "path": "src/config/uiConfig.ts",
    "content": "export const globalInputHeight = 300;\nexport const codeInputHeightOffset = 7; // Offset to visually match Monaco and MUI TextField heights\nexport const globalDescriptionFontSize = 12;\nexport const categoriesColors: string[] = [\n  '#8FBC5D',\n  '#3CB6E2',\n  '#B17F59',\n  '#FFD400',\n  '#AB6993'\n];\n"
  },
  {
    "path": "src/contexts/CustomSnackBarContext.tsx",
    "content": "import { createContext, FC, ReactNode } from 'react';\nimport { Zoom } from '@mui/material';\nimport { useSnackbar } from 'notistack';\n\ntype CustomSnackBarContext = {\n  showSnackBar: (message: string, type: 'error' | 'success') => void;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const CustomSnackBarContext = createContext<CustomSnackBarContext>(\n  {} as CustomSnackBarContext\n);\n\ninterface Props {\n  children: ReactNode;\n}\n\nexport const CustomSnackBarProvider: FC<Props> = ({ children }) => {\n  const { enqueueSnackbar } = useSnackbar();\n  const showSnackBar = (message: string, type: 'error' | 'success') => {\n    enqueueSnackbar(message, {\n      variant: type,\n      anchorOrigin: {\n        vertical: 'top',\n        horizontal: 'right'\n      },\n      TransitionComponent: Zoom\n    });\n  };\n  return (\n    <CustomSnackBarContext.Provider value={{ showSnackBar }}>\n      {children}\n    </CustomSnackBarContext.Provider>\n  );\n};\n"
  },
  {
    "path": "src/datatables/data/material_electrical_properties.ts",
    "content": "export default {\n  title: 'Material Electrical Properties',\n  columns: {\n    resistivity_20c: {\n      title: 'Resistivity at 20°C',\n      type: 'number',\n      unit: 'Ω/m'\n    }\n  },\n  data: {\n    Copper: {\n      resistivity_20c: 1.68e-8\n    },\n    Aluminum: {\n      resistivity_20c: 2.82e-8\n    }\n  }\n};\n"
  },
  {
    "path": "src/datatables/data/wire_gauge.ts",
    "content": "import type { DataTable } from '../types';\n\nconst data: DataTable = {\n  title: 'American Wire Gauge',\n  columns: {\n    diameter: {\n      title: 'Diameter',\n      type: 'number',\n      unit: 'mm'\n    },\n    area: {\n      title: 'Area',\n      type: 'number',\n      unit: 'mm2'\n    }\n  },\n  data: {\n    '0000 AWG': { diameter: 11.684 },\n    '000 AWG': { diameter: 10.405 },\n    '00 AWG': { diameter: 9.266 },\n    '0 AWG': { diameter: 8.251 },\n    '(4/0) AWG': { diameter: 11.684 },\n    '(3/0) AWG': { diameter: 10.405 },\n    '(2/0) AWG': { diameter: 9.266 },\n    '(1/0) AWG': { diameter: 8.251 },\n    '1 AWG': { diameter: 7.348 },\n    '2 AWG': { diameter: 6.544 },\n    '3 AWG': { diameter: 5.827 },\n    '4 AWG': { diameter: 5.189 },\n    '5 AWG': { diameter: 4.621 },\n    '6 AWG': { diameter: 4.115 },\n    '7 AWG': { diameter: 3.665 },\n    '8 AWG': { diameter: 3.264 },\n    '9 AWG': { diameter: 2.906 },\n    '10 AWG': { diameter: 2.588 },\n    '11 AWG': { diameter: 2.305 },\n    '12 AWG': { diameter: 2.053 },\n    '13 AWG': { diameter: 1.828 },\n    '14 AWG': { diameter: 1.628 },\n    '15 AWG': { diameter: 1.45 },\n    '16 AWG': { diameter: 1.291 },\n    '17 AWG': { diameter: 1.15 },\n    '18 AWG': { diameter: 1.024 },\n    '19 AWG': { diameter: 0.912 },\n    '20 AWG': { diameter: 0.812 },\n    '21 AWG': { diameter: 0.723 },\n    '22 AWG': { diameter: 0.644 },\n    '23 AWG': { diameter: 0.573 },\n    '24 AWG': { diameter: 0.511 },\n    '25 AWG': { diameter: 0.455 },\n    '26 AWG': { diameter: 0.405 },\n    '27 AWG': { diameter: 0.361 },\n    '28 AWG': { diameter: 0.321 },\n    '29 AWG': { diameter: 0.286 },\n    '30 AWG': { diameter: 0.255 },\n    '31 AWG': { diameter: 0.227 },\n    '32 AWG': { diameter: 0.202 },\n    '33 AWG': { diameter: 0.18 },\n    '34 AWG': { diameter: 0.16 },\n    '35 AWG': { diameter: 0.143 },\n    '36 AWG': { diameter: 0.127 },\n    '37 AWG': { diameter: 0.113 },\n    '38 AWG': { diameter: 0.101 },\n    '39 AWG': { diameter: 0.0897 },\n    '40 AWG': { diameter: 0.0799 }\n  }\n};\n\nfor (const key in data.data) {\n  data.data[key].area = Math.PI * (data.data[key].diameter / 2) ** 2;\n}\n\nexport default data;\n"
  },
  {
    "path": "src/datatables/index.ts",
    "content": "import type { DataTable } from './types.ts';\n\n/* Used in case later we want any kind of computed extra data */\nexport function dataTableLookup(table: DataTable, key: string): any {\n  return table.data[key];\n}\n\nexport { DataTable };\n"
  },
  {
    "path": "src/datatables/types.ts",
    "content": "/*\nRepresents a set of rows indexed by a key.\nUsed for calculator presets\n\n*/\nexport interface DataTable {\n  title: string;\n  /* A JSON schema properties  */\n  columns: {\n    [key: string]: { title: string; type: string; unit: string };\n  };\n  data: {\n    [key: string]: {\n      [key: string]: any;\n    };\n  };\n}\n"
  },
  {
    "path": "src/hooks/index.ts",
    "content": "export { default as useDebounce } from './useDebounce';\nexport { default as useTimeout } from './useTimeout';\nexport { default as usePrevious } from './usePrevious';\nexport { default as useUpdateEffect } from './useUpdateEffect';\n"
  },
  {
    "path": "src/hooks/useDebounce.ts",
    "content": "import { useCallback, useEffect, useRef } from 'react';\nimport _ from 'lodash';\n\n/**\n * Debounce hook.\n * @param {T} callback\n * @param {number} delay\n * @returns {T}\n */\nfunction useDebounce<T extends (...args: never[]) => void>(\n  callback: T,\n  delay: number\n): T {\n  const callbackRef = useRef<T>(callback);\n\n  // Update the current callback each time it changes.\n  useEffect(() => {\n    callbackRef.current = callback;\n  }, [callback]);\n\n  const debouncedFn = useCallback(\n    _.debounce((...args: never[]) => {\n      callbackRef.current(...args);\n    }, delay),\n    [delay]\n  );\n\n  useEffect(() => {\n    // Cleanup function to cancel any pending debounced calls\n    return () => {\n      debouncedFn.cancel();\n    };\n  }, [debouncedFn]);\n\n  return debouncedFn as unknown as T;\n}\n\nexport default useDebounce;\n"
  },
  {
    "path": "src/hooks/usePrevious.ts",
    "content": "import { useEffect, useRef } from 'react';\n\n/**\n * The usePrevious function is a custom hook that returns the previous value of a variable.\n * It takes in a value as a parameter and returns the previous value.\n */\nfunction usePrevious<T>(value: T): T | undefined {\n  const ref = useRef<T | undefined>();\n\n  // Store current value in ref\n  useEffect(() => {\n    ref.current = value;\n  }, [value]);\n\n  // Return previous value (happens before update in useEffect above)\n  return ref.current;\n}\n\nexport default usePrevious;\n"
  },
  {
    "path": "src/hooks/useTimeout.ts",
    "content": "import { useEffect, useRef } from 'react';\n\n/**\n * The useTimeout function is a custom hook that sets a timeout for a given callback function.\n * It takes in a callback function and a delay time in milliseconds as parameters.\n * It returns nothing.\n */\nfunction useTimeout(callback: () => void, delay: number) {\n  const callbackRef = useRef(callback);\n\n  useEffect(() => {\n    callbackRef.current = callback;\n  }, [callback]);\n\n  useEffect(() => {\n    let timer: NodeJS.Timeout | undefined;\n\n    if (delay !== null && callback && typeof callback === 'function') {\n      timer = setTimeout(callbackRef.current, delay);\n    }\n\n    return () => {\n      if (timer) {\n        clearTimeout(timer);\n      }\n    };\n  }, [callback, delay]);\n}\n\nexport default useTimeout;\n"
  },
  {
    "path": "src/hooks/useUpdateEffect.ts",
    "content": "import { DependencyList, EffectCallback, useEffect, useRef } from 'react';\n\n/**\n * The useUpdateEffect function is a custom hook that behaves like useEffect, but only runs on updates and not on initial mount.\n * It takes in an effect function and an optional dependency list as parameters.\n * It returns nothing.\n */\nconst useUpdateEffect = (effect: EffectCallback, deps?: DependencyList) => {\n  const isInitialMount = useRef(true);\n\n  useEffect(() => {\n    if (isInitialMount.current) {\n      isInitialMount.current = false;\n    }\n    return effect();\n  }, deps);\n};\n\nexport default useUpdateEffect;\n"
  },
  {
    "path": "src/i18n/index.ts",
    "content": "import i18n, { Namespace, ParseKeys } from 'i18next';\nimport { initReactI18next } from 'react-i18next';\nimport Backend from 'i18next-http-backend';\nimport LanguageDetector from 'i18next-browser-languagedetector';\n\nexport const validNamespaces = [\n  'string',\n  'number',\n  'video',\n  'list',\n  'json',\n  'time',\n  'csv',\n  'pdf',\n  'audio',\n  'xml',\n  'translation',\n  'image',\n  'converters'\n] as const satisfies readonly Namespace[];\n\nexport type I18nNamespaces = (typeof validNamespaces)[number];\nexport type FullI18nKey = {\n  [K in I18nNamespaces]: `${K}:${ParseKeys<K>}`;\n}[I18nNamespaces];\n\ni18n\n  .use(Backend)\n  .use(LanguageDetector)\n  .use(initReactI18next)\n  .init({\n    supportedLngs: ['en', 'de', 'es', 'fr', 'pt', 'ja', 'hi', 'nl', 'ru', 'zh'],\n    fallbackLng: 'en',\n    interpolation: {\n      escapeValue: false // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape\n    },\n    backend: {\n      loadPath: '/locales/{{lng}}/{{ns}}.json'\n    },\n    detection: {\n      lookupLocalStorage: 'lang',\n      caches: ['localStorage'] // cache the detected lang back to localStorage\n    }\n  });\n\nexport default i18n;\n"
  },
  {
    "path": "src/index.tsx",
    "content": "import { createRoot } from 'react-dom/client';\nimport 'tailwindcss/tailwind.css';\nimport App from 'components/App';\n\nconst container = document.getElementById('root') as HTMLDivElement;\nconst root = createRoot(container);\n\nroot.render(<App />);\n"
  },
  {
    "path": "src/lib/ghostscript/background-worker.js",
    "content": "import { COMPRESS_ACTION, PROTECT_ACTION } from './worker-init';\n\nfunction loadScript() {\n  import('./gs-worker.js');\n}\n\nvar Module;\n\nfunction compressPdf(dataStruct, responseCallback) {\n  const compressionLevel = dataStruct.compressionLevel || 'medium';\n\n  // Set PDF settings based on compression level\n  let pdfSettings;\n  switch (compressionLevel) {\n    case 'low':\n      pdfSettings = '/printer'; // Higher quality, less compression\n      break;\n    case 'medium':\n      pdfSettings = '/ebook'; // Medium quality and compression\n      break;\n    case 'high':\n      pdfSettings = '/screen'; // Lower quality, higher compression\n      break;\n    default:\n      pdfSettings = '/ebook'; // Default to medium\n  }\n  // first download the ps data\n  var xhr = new XMLHttpRequest();\n  xhr.open('GET', dataStruct.psDataURL);\n  xhr.responseType = 'arraybuffer';\n  xhr.onload = function () {\n    console.log('onload');\n    // release the URL\n    self.URL.revokeObjectURL(dataStruct.psDataURL);\n    //set up EMScripten environment\n    Module = {\n      preRun: [\n        function () {\n          self.Module.FS.writeFile('input.pdf', new Uint8Array(xhr.response));\n        }\n      ],\n      postRun: [\n        function () {\n          var uarray = self.Module.FS.readFile('output.pdf', {\n            encoding: 'binary'\n          });\n          var blob = new Blob([uarray], { type: 'application/octet-stream' });\n          var pdfDataURL = self.URL.createObjectURL(blob);\n          responseCallback({\n            pdfDataURL: pdfDataURL,\n            url: dataStruct.url,\n            type: COMPRESS_ACTION\n          });\n        }\n      ],\n      arguments: [\n        '-sDEVICE=pdfwrite',\n        '-dCompatibilityLevel=1.4',\n        `-dPDFSETTINGS=${pdfSettings}`,\n        '-DNOPAUSE',\n        '-dQUIET',\n        '-dBATCH',\n        '-sOutputFile=output.pdf',\n        'input.pdf'\n      ],\n      print: function (text) {},\n      printErr: function (text) {},\n      totalDependencies: 0,\n      noExitRuntime: 1\n    };\n    // Module.setStatus(\"Loading Ghostscript...\");\n    if (!self.Module) {\n      self.Module = Module;\n      loadScript();\n    } else {\n      self.Module['calledRun'] = false;\n      self.Module['postRun'] = Module.postRun;\n      self.Module['preRun'] = Module.preRun;\n      self.Module.callMain();\n    }\n  };\n  xhr.send();\n}\n\nfunction protectPdf(dataStruct, responseCallback) {\n  const password = dataStruct.password || '';\n\n  // Validate password\n  if (!password) {\n    responseCallback({\n      error: 'Password is required for encryption',\n      url: dataStruct.url\n    });\n    return;\n  }\n  var xhr = new XMLHttpRequest();\n  xhr.open('GET', dataStruct.psDataURL);\n  xhr.responseType = 'arraybuffer';\n  xhr.onload = function () {\n    console.log('onload');\n    // release the URL\n    self.URL.revokeObjectURL(dataStruct.psDataURL);\n    //set up EMScripten environment\n    Module = {\n      preRun: [\n        function () {\n          self.Module.FS.writeFile('input.pdf', new Uint8Array(xhr.response));\n        }\n      ],\n      postRun: [\n        function () {\n          var uarray = self.Module.FS.readFile('output.pdf', {\n            encoding: 'binary'\n          });\n          var blob = new Blob([uarray], { type: 'application/octet-stream' });\n          var pdfDataURL = self.URL.createObjectURL(blob);\n          responseCallback({\n            pdfDataURL: pdfDataURL,\n            url: dataStruct.url,\n            type: PROTECT_ACTION\n          });\n        }\n      ],\n      arguments: [\n        '-sDEVICE=pdfwrite',\n        '-dCompatibilityLevel=1.4',\n        `-sOwnerPassword=${password}`,\n        `-sUserPassword=${password}`,\n        // Permissions (prevent copying/printing/etc)\n        '-dEncryptionPermissions=-4',\n        '-DNOPAUSE',\n        '-dQUIET',\n        '-dBATCH',\n        '-sOutputFile=output.pdf',\n        'input.pdf'\n      ],\n      print: function (text) {},\n      printErr: function (text) {},\n      totalDependencies: 0,\n      noExitRuntime: 1\n    };\n    // Module.setStatus(\"Loading Ghostscript...\");\n    if (!self.Module) {\n      self.Module = Module;\n      loadScript();\n    } else {\n      self.Module['calledRun'] = false;\n      self.Module['postRun'] = Module.postRun;\n      self.Module['preRun'] = Module.preRun;\n      self.Module.callMain();\n    }\n  };\n  xhr.send();\n}\n\nself.addEventListener('message', function ({ data: e }) {\n  console.log('message', e);\n  // e.data contains the message sent to the worker.\n  if (e.target !== 'wasm') {\n    return;\n  }\n  console.log('Message received from main script', e.data);\n  const responseCallback = ({ pdfDataURL, type }) => {\n    self.postMessage(pdfDataURL);\n  };\n  if (e.data.type === COMPRESS_ACTION) {\n    compressPdf(e.data, responseCallback);\n  } else if (e.data.type === PROTECT_ACTION) {\n    protectPdf(e.data, responseCallback);\n  }\n});\n\nconsole.log('Worker ready');\n"
  },
  {
    "path": "src/lib/ghostscript/gs-worker.js",
    "content": "// include: shell.js\n// The Module object: Our interface to the outside world. We import\n// and export values on it. There are various ways Module can be used:\n// 1. Not defined. We create it here\n// 2. A function parameter, function(Module) { ..generated code.. }\n// 3. pre-run appended it, var Module = {}; ..generated code..\n// 4. External script tag defines var Module.\n// We need to check if Module already exists (e.g. case 3 above).\n// Substitution will be replaced with actual code on later stage of the build,\n// this way Closure Compiler will not mangle it (e.g. case 4. above).\n// Note that if you want to run closure, and also to use Module\n// after the generated code, you will need to define   var Module = {};\n// before the code. Then that object will be used in the code, and you\n// can continue to use Module afterwards as well.\nvar Module =\n  typeof Module != 'undefined' ? Module : self.Module ? self.Module : {};\n\n// --pre-jses are emitted after the Module integration code, so that they can\n// refer to Module (if they choose; they can also define Module)\n\n// Sometimes an existing Module object exists with properties\n// meant to overwrite the default module functionality. Here\n// we collect those properties and reapply _after_ we configure\n// the current environment's defaults to avoid having to be so\n// defensive during initialization.\nvar moduleOverrides = Object.assign({}, Module);\n\nvar arguments_ = [];\nvar thisProgram = './this.program';\nvar quit_ = (status, toThrow) => {\n  throw toThrow;\n};\n\n// Determine the runtime environment we are in. You can customize this by\n// setting the ENVIRONMENT setting at compile time (see settings.js).\n\n// Attempt to auto-detect the environment\nvar ENVIRONMENT_IS_WEB = typeof window == 'object';\nvar ENVIRONMENT_IS_WORKER = typeof importScripts == 'function';\n// N.b. Electron.js environment is simultaneously a NODE-environment, but\n// also a web environment.\nvar ENVIRONMENT_IS_NODE =\n  typeof process == 'object' &&\n  typeof process.versions == 'object' &&\n  typeof process.versions.node == 'string';\nvar ENVIRONMENT_IS_SHELL =\n  !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;\n\nif (Module['ENVIRONMENT']) {\n  throw new Error(\n    'Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)'\n  );\n}\n\n// `/` should be present at the end if `scriptDirectory` is not empty\nvar scriptDirectory = '';\nfunction locateFile(path) {\n  if (Module['locateFile']) {\n    return Module['locateFile'](path, scriptDirectory);\n  }\n  return scriptDirectory + path;\n}\n\n// Hooks that are implemented differently in different runtime environments.\nvar read_, readAsync, readBinary;\n\nif (ENVIRONMENT_IS_NODE) {\n  if (\n    typeof process == 'undefined' ||\n    !process.release ||\n    process.release.name !== 'node'\n  )\n    throw new Error(\n      'not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'\n    );\n\n  var nodeVersion = process.versions.node;\n  var numericVersion = nodeVersion.split('.').slice(0, 3);\n  numericVersion =\n    numericVersion[0] * 10000 +\n    numericVersion[1] * 100 +\n    numericVersion[2].split('-')[0] * 1;\n  var minVersion = 160000;\n  if (numericVersion < 160000) {\n    throw new Error(\n      'This emscripten-generated code requires node v16.0.0 (detected v' +\n        nodeVersion +\n        ')'\n    );\n  }\n\n  // `require()` is no-op in an ESM module, use `createRequire()` to construct\n  // the require()` function.  This is only necessary for multi-environment\n  // builds, `-sENVIRONMENT=node` emits a static import declaration instead.\n  // TODO: Swap all `require()`'s with `import()`'s?\n  // These modules will usually be used on Node.js. Load them eagerly to avoid\n  // the complexity of lazy-loading.\n  var fs = require('fs');\n  var nodePath = require('path');\n\n  if (ENVIRONMENT_IS_WORKER) {\n    scriptDirectory = nodePath.dirname(scriptDirectory) + '/';\n  } else {\n    scriptDirectory = __dirname + '/';\n  }\n\n  // include: node_shell_read.js\n  read_ = (filename, binary) => {\n    // We need to re-wrap `file://` strings to URLs. Normalizing isn't\n    // necessary in that case, the path should already be absolute.\n    filename = isFileURI(filename)\n      ? new URL(filename)\n      : nodePath.normalize(filename);\n    return fs.readFileSync(filename, binary ? undefined : 'utf8');\n  };\n\n  readBinary = (filename) => {\n    var ret = read_(filename, true);\n    if (!ret.buffer) {\n      ret = new Uint8Array(ret);\n    }\n    assert(ret.buffer);\n    return ret;\n  };\n\n  readAsync = (filename, onload, onerror, binary = true) => {\n    // See the comment in the `read_` function.\n    filename = isFileURI(filename)\n      ? new URL(filename)\n      : nodePath.normalize(filename);\n    fs.readFile(filename, binary ? undefined : 'utf8', (err, data) => {\n      if (err) onerror(err);\n      else onload(binary ? data.buffer : data);\n    });\n  };\n  // end include: node_shell_read.js\n  if (!Module['thisProgram'] && process.argv.length > 1) {\n    thisProgram = process.argv[1].replace(/\\\\/g, '/');\n  }\n\n  arguments_ = process.argv.slice(2);\n\n  if (typeof module != 'undefined') {\n    module['exports'] = Module;\n  }\n\n  process.on('uncaughtException', (ex) => {\n    // suppress ExitStatus exceptions from showing an error\n    if (\n      ex !== 'unwind' &&\n      !(ex instanceof ExitStatus) &&\n      !(ex.context instanceof ExitStatus)\n    ) {\n      throw ex;\n    }\n  });\n\n  quit_ = (status, toThrow) => {\n    process.exitCode = status;\n    throw toThrow;\n  };\n} else if (ENVIRONMENT_IS_SHELL) {\n  if (\n    (typeof process == 'object' && typeof require === 'function') ||\n    typeof window == 'object' ||\n    typeof importScripts == 'function'\n  )\n    throw new Error(\n      'not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'\n    );\n\n  if (typeof read != 'undefined') {\n    read_ = read;\n  }\n\n  readBinary = (f) => {\n    if (typeof readbuffer == 'function') {\n      return new Uint8Array(readbuffer(f));\n    }\n    let data = read(f, 'binary');\n    assert(typeof data == 'object');\n    return data;\n  };\n\n  readAsync = (f, onload, onerror) => {\n    setTimeout(() => onload(readBinary(f)));\n  };\n\n  if (typeof clearTimeout == 'undefined') {\n    globalThis.clearTimeout = (id) => {};\n  }\n\n  if (typeof setTimeout == 'undefined') {\n    // spidermonkey lacks setTimeout but we use it above in readAsync.\n    globalThis.setTimeout = (f) => (typeof f == 'function' ? f() : abort());\n  }\n\n  if (typeof scriptArgs != 'undefined') {\n    arguments_ = scriptArgs;\n  } else if (typeof arguments != 'undefined') {\n    arguments_ = arguments;\n  }\n\n  if (typeof quit == 'function') {\n    quit_ = (status, toThrow) => {\n      // Unlike node which has process.exitCode, d8 has no such mechanism. So we\n      // have no way to set the exit code and then let the program exit with\n      // that code when it naturally stops running (say, when all setTimeouts\n      // have completed). For that reason, we must call `quit` - the only way to\n      // set the exit code - but quit also halts immediately.  To increase\n      // consistency with node (and the web) we schedule the actual quit call\n      // using a setTimeout to give the current stack and any exception handlers\n      // a chance to run.  This enables features such as addOnPostRun (which\n      // expected to be able to run code after main returns).\n      setTimeout(() => {\n        if (!(toThrow instanceof ExitStatus)) {\n          let toLog = toThrow;\n          if (toThrow && typeof toThrow == 'object' && toThrow.stack) {\n            toLog = [toThrow, toThrow.stack];\n          }\n          err(`exiting due to exception: ${toLog}`);\n        }\n        quit(status);\n      });\n      throw toThrow;\n    };\n  }\n\n  if (typeof print != 'undefined') {\n    // Prefer to use print/printErr where they exist, as they usually work better.\n    if (typeof console == 'undefined') console = /** @type{!Console} */ ({});\n    console.log = /** @type{!function(this:Console, ...*): undefined} */ (\n      print\n    );\n    console.warn = console.error =\n      /** @type{!function(this:Console, ...*): undefined} */ (\n        typeof printErr != 'undefined' ? printErr : print\n      );\n  }\n}\n\n// Note that this includes Node.js workers when relevant (pthreads is enabled).\n// Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and\n// ENVIRONMENT_IS_NODE.\nelse if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {\n  if (ENVIRONMENT_IS_WORKER) {\n    // Check worker, not web, since window could be polyfilled\n    scriptDirectory = self.location.href;\n  } else if (typeof document != 'undefined' && document.currentScript) {\n    // web\n    scriptDirectory = document.currentScript.src;\n  }\n  // blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them.\n  // otherwise, slice off the final part of the url to find the script directory.\n  // if scriptDirectory does not contain a slash, lastIndexOf will return -1,\n  // and scriptDirectory will correctly be replaced with an empty string.\n  // If scriptDirectory contains a query (starting with ?) or a fragment (starting with #),\n  // they are removed because they could contain a slash.\n  if (scriptDirectory.startsWith('blob:')) {\n    scriptDirectory = '';\n  } else {\n    scriptDirectory = scriptDirectory.substr(\n      0,\n      scriptDirectory.replace(/[?#].*/, '').lastIndexOf('/') + 1\n    );\n  }\n\n  if (!(typeof window == 'object' || typeof importScripts == 'function'))\n    throw new Error(\n      'not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'\n    );\n\n  // Differentiate the Web Worker from the Node Worker case, as reading must\n  // be done differently.\n  {\n    // include: web_or_worker_shell_read.js\n    read_ = (url) => {\n      var xhr = new XMLHttpRequest();\n      xhr.open('GET', url, false);\n      xhr.send(null);\n      return xhr.responseText;\n    };\n\n    if (ENVIRONMENT_IS_WORKER) {\n      readBinary = (url) => {\n        var xhr = new XMLHttpRequest();\n        xhr.open('GET', url, false);\n        xhr.responseType = 'arraybuffer';\n        xhr.send(null);\n        return new Uint8Array(/** @type{!ArrayBuffer} */ (xhr.response));\n      };\n    }\n\n    readAsync = (url, onload, onerror) => {\n      var xhr = new XMLHttpRequest();\n      xhr.open('GET', url, true);\n      xhr.responseType = 'arraybuffer';\n      xhr.onload = () => {\n        if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) {\n          // file URLs can return 0\n          onload(xhr.response);\n          return;\n        }\n        onerror();\n      };\n      xhr.onerror = onerror;\n      xhr.send(null);\n    };\n\n    // end include: web_or_worker_shell_read.js\n  }\n} else {\n  throw new Error('environment detection error');\n}\n\nvar out = Module['print'] || console.log.bind(console);\nvar err = Module['printErr'] || console.error.bind(console);\n\n// Merge back in the overrides\nObject.assign(Module, moduleOverrides);\n// Free the object hierarchy contained in the overrides, this lets the GC\n// reclaim data used.\nmoduleOverrides = null;\ncheckIncomingModuleAPI();\n\n// Emit code to handle expected values on the Module object. This applies Module.x\n// to the proper local x. This has two benefits: first, we only emit it if it is\n// expected to arrive, and second, by using a local everywhere else that can be\n// minified.\n\nif (Module['arguments']) arguments_ = Module['arguments'];\nlegacyModuleProp('arguments', 'arguments_');\n\nif (Module['thisProgram']) thisProgram = Module['thisProgram'];\nlegacyModuleProp('thisProgram', 'thisProgram');\n\nif (Module['quit']) quit_ = Module['quit'];\nlegacyModuleProp('quit', 'quit_');\n\n// perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message\n// Assertions on removed incoming Module JS APIs.\nassert(\n  typeof Module['memoryInitializerPrefixURL'] == 'undefined',\n  'Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead'\n);\nassert(\n  typeof Module['pthreadMainPrefixURL'] == 'undefined',\n  'Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead'\n);\nassert(\n  typeof Module['cdInitializerPrefixURL'] == 'undefined',\n  'Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead'\n);\nassert(\n  typeof Module['filePackagePrefixURL'] == 'undefined',\n  'Module.filePackagePrefixURL option was removed, use Module.locateFile instead'\n);\nassert(\n  typeof Module['read'] == 'undefined',\n  'Module.read option was removed (modify read_ in JS)'\n);\nassert(\n  typeof Module['readAsync'] == 'undefined',\n  'Module.readAsync option was removed (modify readAsync in JS)'\n);\nassert(\n  typeof Module['readBinary'] == 'undefined',\n  'Module.readBinary option was removed (modify readBinary in JS)'\n);\nassert(\n  typeof Module['setWindowTitle'] == 'undefined',\n  'Module.setWindowTitle option was removed (modify emscripten_set_window_title in JS)'\n);\nassert(\n  typeof Module['TOTAL_MEMORY'] == 'undefined',\n  'Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY'\n);\nlegacyModuleProp('asm', 'wasmExports');\nlegacyModuleProp('read', 'read_');\nlegacyModuleProp('readAsync', 'readAsync');\nlegacyModuleProp('readBinary', 'readBinary');\nlegacyModuleProp('setWindowTitle', 'setWindowTitle');\nvar IDBFS = 'IDBFS is no longer included by default; build with -lidbfs.js';\nvar PROXYFS =\n  'PROXYFS is no longer included by default; build with -lproxyfs.js';\nvar WORKERFS =\n  'WORKERFS is no longer included by default; build with -lworkerfs.js';\nvar FETCHFS =\n  'FETCHFS is no longer included by default; build with -lfetchfs.js';\nvar ICASEFS =\n  'ICASEFS is no longer included by default; build with -licasefs.js';\nvar JSFILEFS =\n  'JSFILEFS is no longer included by default; build with -ljsfilefs.js';\nvar OPFS = 'OPFS is no longer included by default; build with -lopfs.js';\n\nvar NODEFS = 'NODEFS is no longer included by default; build with -lnodefs.js';\n\nassert(\n  !ENVIRONMENT_IS_SHELL,\n  'shell environment detected but not enabled at build time.  Add `shell` to `-sENVIRONMENT` to enable.'\n);\n\n// end include: shell.js\n\n// include: preamble.js\n// === Preamble library stuff ===\n\n// Documentation for the public APIs defined in this file must be updated in:\n//    site/source/docs/api_reference/preamble.js.rst\n// A prebuilt local version of the documentation is available at:\n//    site/build/text/docs/api_reference/preamble.js.txt\n// You can also build docs locally as HTML or other formats in site/\n// An online HTML version (which may be of a different version of Emscripten)\n//    is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html\n\nvar wasmBinary;\nif (Module['wasmBinary']) wasmBinary = Module['wasmBinary'];\nlegacyModuleProp('wasmBinary', 'wasmBinary');\n\nif (typeof WebAssembly != 'object') {\n  err('no native wasm support detected');\n}\n\n// Wasm globals\n\nvar wasmMemory;\n\n//========================================\n// Runtime essentials\n//========================================\n\n// whether we are quitting the application. no code should run after this.\n// set in exit() and abort()\nvar ABORT = false;\n\n// set by exit() and abort().  Passed to 'onExit' handler.\n// NOTE: This is also used as the process return code code in shell environments\n// but only when noExitRuntime is false.\nvar EXITSTATUS;\n\n// In STRICT mode, we only define assert() when ASSERTIONS is set.  i.e. we\n// don't define it at all in release modes.  This matches the behaviour of\n// MINIMAL_RUNTIME.\n// TODO(sbc): Make this the default even without STRICT enabled.\n/** @type {function(*, string=)} */\nfunction assert(condition, text) {\n  if (!condition) {\n    abort('Assertion failed' + (text ? ': ' + text : ''));\n  }\n}\n\n// We used to include malloc/free by default in the past. Show a helpful error in\n// builds with assertions.\n\n// Memory management\n\nvar HEAP,\n  /** @type {!Int8Array} */\n  HEAP8,\n  /** @type {!Uint8Array} */\n  HEAPU8,\n  /** @type {!Int16Array} */\n  HEAP16,\n  /** @type {!Uint16Array} */\n  HEAPU16,\n  /** @type {!Int32Array} */\n  HEAP32,\n  /** @type {!Uint32Array} */\n  HEAPU32,\n  /** @type {!Float32Array} */\n  HEAPF32,\n  /** @type {!Float64Array} */\n  HEAPF64;\n\n// include: runtime_shared.js\nfunction updateMemoryViews() {\n  var b = wasmMemory.buffer;\n  Module['HEAP8'] = HEAP8 = new Int8Array(b);\n  Module['HEAP16'] = HEAP16 = new Int16Array(b);\n  Module['HEAPU8'] = HEAPU8 = new Uint8Array(b);\n  Module['HEAPU16'] = HEAPU16 = new Uint16Array(b);\n  Module['HEAP32'] = HEAP32 = new Int32Array(b);\n  Module['HEAPU32'] = HEAPU32 = new Uint32Array(b);\n  Module['HEAPF32'] = HEAPF32 = new Float32Array(b);\n  Module['HEAPF64'] = HEAPF64 = new Float64Array(b);\n}\n// end include: runtime_shared.js\nassert(\n  !Module['STACK_SIZE'],\n  'STACK_SIZE can no longer be set at runtime.  Use -sSTACK_SIZE at link time'\n);\n\nassert(\n  typeof Int32Array != 'undefined' &&\n    typeof Float64Array !== 'undefined' &&\n    Int32Array.prototype.subarray != undefined &&\n    Int32Array.prototype.set != undefined,\n  'JS engine does not provide full typed array support'\n);\n\n// If memory is defined in wasm, the user can't provide it, or set INITIAL_MEMORY\nassert(\n  !Module['wasmMemory'],\n  'Use of `wasmMemory` detected.  Use -sIMPORTED_MEMORY to define wasmMemory externally'\n);\nassert(\n  !Module['INITIAL_MEMORY'],\n  'Detected runtime INITIAL_MEMORY setting.  Use -sIMPORTED_MEMORY to define wasmMemory dynamically'\n);\n\n// include: runtime_stack_check.js\n// Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode.\nfunction writeStackCookie() {\n  var max = _emscripten_stack_get_end();\n  assert((max & 3) == 0);\n  // If the stack ends at address zero we write our cookies 4 bytes into the\n  // stack.  This prevents interference with SAFE_HEAP and ASAN which also\n  // monitor writes to address zero.\n  if (max == 0) {\n    max += 4;\n  }\n  // The stack grow downwards towards _emscripten_stack_get_end.\n  // We write cookies to the final two words in the stack and detect if they are\n  // ever overwritten.\n  HEAPU32[max >> 2] = 0x02135467;\n  HEAPU32[(max + 4) >> 2] = 0x89bacdfe;\n  // Also test the global address 0 for integrity.\n  HEAPU32[0 >> 2] = 1668509029;\n}\n\nfunction checkStackCookie() {\n  if (ABORT) return;\n  var max = _emscripten_stack_get_end();\n  // See writeStackCookie().\n  if (max == 0) {\n    max += 4;\n  }\n  var cookie1 = HEAPU32[max >> 2];\n  var cookie2 = HEAPU32[(max + 4) >> 2];\n  if (cookie1 != 0x02135467 || cookie2 != 0x89bacdfe) {\n    abort(\n      `Stack overflow! Stack cookie has been overwritten at ${ptrToString(\n        max\n      )}, expected hex dwords 0x89BACDFE and 0x2135467, but received ${ptrToString(\n        cookie2\n      )} ${ptrToString(cookie1)}`\n    );\n  }\n  // Also test the global address 0 for integrity.\n  if (HEAPU32[0 >> 2] != 0x63736d65 /* 'emsc' */) {\n    abort(\n      'Runtime error: The application has corrupted its heap memory area (address zero)!'\n    );\n  }\n}\n// end include: runtime_stack_check.js\n// include: runtime_assertions.js\n// Endianness check\n(function () {\n  var h16 = new Int16Array(1);\n  var h8 = new Int8Array(h16.buffer);\n  h16[0] = 0x6373;\n  if (h8[0] !== 0x73 || h8[1] !== 0x63)\n    throw 'Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)';\n})();\n\n// end include: runtime_assertions.js\nvar __ATPRERUN__ = []; // functions called before the runtime is initialized\nvar __ATINIT__ = []; // functions called during startup\nvar __ATMAIN__ = []; // functions called when main() is to be run\nvar __ATEXIT__ = []; // functions called during shutdown\nvar __ATPOSTRUN__ = []; // functions called after the main() is called\n\nvar runtimeInitialized = false;\n\nvar runtimeExited = false;\n\nfunction preRun() {\n  if (Module['preRun']) {\n    if (typeof Module['preRun'] == 'function')\n      Module['preRun'] = [Module['preRun']];\n    while (Module['preRun'].length) {\n      addOnPreRun(Module['preRun'].shift());\n    }\n  }\n  callRuntimeCallbacks(__ATPRERUN__);\n}\n\nfunction initRuntime() {\n  assert(!runtimeInitialized);\n  runtimeInitialized = true;\n\n  checkStackCookie();\n\n  if (!Module['noFSInit'] && !FS.init.initialized) FS.init();\n  FS.ignorePermissions = false;\n\n  TTY.init();\n  callRuntimeCallbacks(__ATINIT__);\n}\n\nfunction preMain() {\n  checkStackCookie();\n\n  callRuntimeCallbacks(__ATMAIN__);\n}\n\nfunction exitRuntime() {\n  assert(!runtimeExited);\n  checkStackCookie();\n  ___funcs_on_exit(); // Native atexit() functions\n  callRuntimeCallbacks(__ATEXIT__);\n  FS.quit();\n  TTY.shutdown();\n  runtimeExited = true;\n}\n\nfunction postRun() {\n  checkStackCookie();\n\n  if (Module['postRun']) {\n    if (typeof Module['postRun'] == 'function')\n      Module['postRun'] = [Module['postRun']];\n    while (Module['postRun'].length) {\n      addOnPostRun(Module['postRun'].shift());\n    }\n  }\n\n  callRuntimeCallbacks(__ATPOSTRUN__);\n}\n\nfunction addOnPreRun(cb) {\n  __ATPRERUN__.unshift(cb);\n}\n\nfunction addOnInit(cb) {\n  __ATINIT__.unshift(cb);\n}\n\nfunction addOnPreMain(cb) {\n  __ATMAIN__.unshift(cb);\n}\n\nfunction addOnExit(cb) {\n  __ATEXIT__.unshift(cb);\n}\n\nfunction addOnPostRun(cb) {\n  __ATPOSTRUN__.unshift(cb);\n}\n\n// include: runtime_math.js\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul\n\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround\n\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32\n\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc\n\nassert(\n  Math.imul,\n  'This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'\n);\nassert(\n  Math.fround,\n  'This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'\n);\nassert(\n  Math.clz32,\n  'This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'\n);\nassert(\n  Math.trunc,\n  'This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'\n);\n// end include: runtime_math.js\n// A counter of dependencies for calling run(). If we need to\n// do asynchronous work before running, increment this and\n// decrement it. Incrementing must happen in a place like\n// Module.preRun (used by emcc to add file preloading).\n// Note that you can add dependencies in preRun, even though\n// it happens right before run - run will be postponed until\n// the dependencies are met.\nvar runDependencies = 0;\nvar runDependencyWatcher = null;\nvar dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled\nvar runDependencyTracking = {};\n\nfunction getUniqueRunDependency(id) {\n  var orig = id;\n  while (1) {\n    if (!runDependencyTracking[id]) return id;\n    id = orig + Math.random();\n  }\n}\n\nfunction addRunDependency(id) {\n  runDependencies++;\n\n  Module['monitorRunDependencies']?.(runDependencies);\n\n  if (id) {\n    assert(!runDependencyTracking[id]);\n    runDependencyTracking[id] = 1;\n    if (runDependencyWatcher === null && typeof setInterval != 'undefined') {\n      // Check for missing dependencies every few seconds\n      runDependencyWatcher = setInterval(() => {\n        if (ABORT) {\n          clearInterval(runDependencyWatcher);\n          runDependencyWatcher = null;\n          return;\n        }\n        var shown = false;\n        for (var dep in runDependencyTracking) {\n          if (!shown) {\n            shown = true;\n            err('still waiting on run dependencies:');\n          }\n          err(`dependency: ${dep}`);\n        }\n        if (shown) {\n          err('(end of list)');\n        }\n      }, 10000);\n    }\n  } else {\n    err('warning: run dependency added without ID');\n  }\n}\n\nfunction removeRunDependency(id) {\n  runDependencies--;\n\n  Module['monitorRunDependencies']?.(runDependencies);\n\n  if (id) {\n    assert(runDependencyTracking[id]);\n    delete runDependencyTracking[id];\n  } else {\n    err('warning: run dependency removed without ID');\n  }\n  if (runDependencies == 0) {\n    if (runDependencyWatcher !== null) {\n      clearInterval(runDependencyWatcher);\n      runDependencyWatcher = null;\n    }\n    if (dependenciesFulfilled) {\n      var callback = dependenciesFulfilled;\n      dependenciesFulfilled = null;\n      callback(); // can add another dependenciesFulfilled\n    }\n  }\n}\n\n/** @param {string|number=} what */\nfunction abort(what) {\n  Module['onAbort']?.(what);\n\n  what = 'Aborted(' + what + ')';\n  // TODO(sbc): Should we remove printing and leave it up to whoever\n  // catches the exception?\n  err(what);\n\n  ABORT = true;\n  EXITSTATUS = 1;\n\n  // Use a wasm runtime error, because a JS error might be seen as a foreign\n  // exception, which means we'd run destructors on it. We need the error to\n  // simply make the program stop.\n  // FIXME This approach does not work in Wasm EH because it currently does not assume\n  // all RuntimeErrors are from traps; it decides whether a RuntimeError is from\n  // a trap or not based on a hidden field within the object. So at the moment\n  // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that\n  // allows this in the wasm spec.\n\n  // Suppress closure compiler warning here. Closure compiler's builtin extern\n  // definition for WebAssembly.RuntimeError claims it takes no arguments even\n  // though it can.\n  // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed.\n  /** @suppress {checkTypes} */\n  var e = new WebAssembly.RuntimeError(what);\n\n  // Throw the error whether or not MODULARIZE is set because abort is used\n  // in code paths apart from instantiation where an exception is expected\n  // to be thrown when abort is called.\n  throw e;\n}\n\n// include: memoryprofiler.js\n// end include: memoryprofiler.js\n// include: URIUtils.js\n// Prefix of data URIs emitted by SINGLE_FILE and related options.\nvar dataURIPrefix = 'data:application/octet-stream;base64,';\n\n/**\n * Indicates whether filename is a base64 data URI.\n * @noinline\n */\nvar isDataURI = (filename) => filename.startsWith(dataURIPrefix);\n\n/**\n * Indicates whether filename is delivered via file protocol (as opposed to http/https)\n * @noinline\n */\nvar isFileURI = (filename) => filename.startsWith('file://');\n// end include: URIUtils.js\nfunction createExportWrapper(name) {\n  return (...args) => {\n    assert(\n      runtimeInitialized,\n      `native function \\`${name}\\` called before runtime initialization`\n    );\n    assert(\n      !runtimeExited,\n      `native function \\`${name}\\` called after runtime exit (use NO_EXIT_RUNTIME to keep it alive after main() exits)`\n    );\n    var f = wasmExports[name];\n    assert(f, `exported native function \\`${name}\\` not found`);\n    return f(...args);\n  };\n}\n\n// include: runtime_exceptions.js\n// end include: runtime_exceptions.js\nvar wasmBinaryFile;\nwasmBinaryFile = 'https://cdn-wasm.b-cdn.net/gs-worker.wasm';\n// if (!isDataURI(wasmBinaryFile)) {\n//   wasmBinaryFile = locateFile(wasmBinaryFile);\n// }\n\nfunction getBinarySync(file) {\n  if (file == wasmBinaryFile && wasmBinary) {\n    return new Uint8Array(wasmBinary);\n  }\n  if (readBinary) {\n    return readBinary(file);\n  }\n  throw 'both async and sync fetching of the wasm failed';\n}\n\nfunction getBinaryPromise(binaryFile) {\n  // If we don't have the binary yet, try to load it asynchronously.\n  // Fetch has some additional restrictions over XHR, like it can't be used on a file:// url.\n  // See https://github.com/github/fetch/pull/92#issuecomment-140665932\n  // Cordova or Electron apps are typically loaded from a file:// url.\n  // So use fetch if it is available and the url is not a file, otherwise fall back to XHR.\n  if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER)) {\n    if (typeof fetch == 'function' && !isFileURI(binaryFile)) {\n      return fetch(binaryFile, { credentials: 'same-origin' })\n        .then((response) => {\n          if (!response['ok']) {\n            throw `failed to load wasm binary file at '${binaryFile}'`;\n          }\n          return response['arrayBuffer']();\n        })\n        .catch(() => getBinarySync(binaryFile));\n    } else if (readAsync) {\n      // fetch is not available or url is file => try XHR (readAsync uses XHR internally)\n      return new Promise((resolve, reject) => {\n        readAsync(\n          binaryFile,\n          (response) =>\n            resolve(new Uint8Array(/** @type{!ArrayBuffer} */ (response))),\n          reject\n        );\n      });\n    }\n  }\n\n  // Otherwise, getBinarySync should be able to get it synchronously\n  return Promise.resolve().then(() => getBinarySync(binaryFile));\n}\n\nfunction instantiateArrayBuffer(binaryFile, imports, receiver) {\n  return getBinaryPromise(binaryFile)\n    .then((binary) => {\n      return WebAssembly.instantiate(binary, imports);\n    })\n    .then(receiver, (reason) => {\n      err(`failed to asynchronously prepare wasm: ${reason}`);\n\n      // Warn on some common problems.\n      if (isFileURI(wasmBinaryFile)) {\n        err(\n          `warning: Loading from a file URI (${wasmBinaryFile}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`\n        );\n      }\n      abort(reason);\n    });\n}\n\nfunction instantiateAsync(binary, binaryFile, imports, callback) {\n  if (\n    !binary &&\n    typeof WebAssembly.instantiateStreaming == 'function' &&\n    !isDataURI(binaryFile) &&\n    // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously.\n    !isFileURI(binaryFile) &&\n    // Avoid instantiateStreaming() on Node.js environment for now, as while\n    // Node.js v18.1.0 implements it, it does not have a full fetch()\n    // implementation yet.\n    //\n    // Reference:\n    //   https://github.com/emscripten-core/emscripten/pull/16917\n    !ENVIRONMENT_IS_NODE &&\n    typeof fetch == 'function'\n  ) {\n    return fetch(binaryFile, { credentials: 'same-origin' }).then(\n      (response) => {\n        // Suppress closure warning here since the upstream definition for\n        // instantiateStreaming only allows Promise<Repsponse> rather than\n        // an actual Response.\n        // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure is fixed.\n        /** @suppress {checkTypes} */\n        var result = WebAssembly.instantiateStreaming(response, imports);\n\n        return result.then(callback, function (reason) {\n          // We expect the most common failure cause to be a bad MIME type for the binary,\n          // in which case falling back to ArrayBuffer instantiation should work.\n          err(`wasm streaming compile failed: ${reason}`);\n          err('falling back to ArrayBuffer instantiation');\n          return instantiateArrayBuffer(binaryFile, imports, callback);\n        });\n      }\n    );\n  }\n  return instantiateArrayBuffer(binaryFile, imports, callback);\n}\n\n// Create the wasm instance.\n// Receives the wasm imports, returns the exports.\nfunction createWasm() {\n  // prepare imports\n  var info = {\n    env: wasmImports,\n    wasi_snapshot_preview1: wasmImports\n  };\n  // Load the wasm module and create an instance of using native support in the JS engine.\n  // handle a generated wasm instance, receiving its exports and\n  // performing other necessary setup\n  /** @param {WebAssembly.Module=} module*/\n  function receiveInstance(instance, module) {\n    wasmExports = instance.exports;\n\n    wasmMemory = wasmExports['memory'];\n\n    assert(wasmMemory, 'memory not found in wasm exports');\n    updateMemoryViews();\n\n    wasmTable = wasmExports['__indirect_function_table'];\n\n    assert(wasmTable, 'table not found in wasm exports');\n\n    addOnInit(wasmExports['__wasm_call_ctors']);\n\n    removeRunDependency('wasm-instantiate');\n    return wasmExports;\n  }\n  // wait for the pthread pool (if any)\n  addRunDependency('wasm-instantiate');\n\n  // Prefer streaming instantiation if available.\n  // Async compilation can be confusing when an error on the page overwrites Module\n  // (for example, if the order of elements is wrong, and the one defining Module is\n  // later), so we save Module and check it later.\n  var trueModule = Module;\n  function receiveInstantiationResult(result) {\n    // 'result' is a ResultObject object which has both the module and instance.\n    // receiveInstance() will swap in the exports (to Module.asm) so they can be called\n    assert(\n      Module === trueModule,\n      'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?'\n    );\n    trueModule = null;\n    // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line.\n    // When the regression is fixed, can restore the above PTHREADS-enabled path.\n    receiveInstance(result['instance']);\n  }\n\n  // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback\n  // to manually instantiate the Wasm module themselves. This allows pages to\n  // run the instantiation parallel to any other async startup actions they are\n  // performing.\n  // Also pthreads and wasm workers initialize the wasm instance through this\n  // path.\n  if (Module['instantiateWasm']) {\n    try {\n      return Module['instantiateWasm'](info, receiveInstance);\n    } catch (e) {\n      err(`Module.instantiateWasm callback failed with error: ${e}`);\n      return false;\n    }\n  }\n\n  instantiateAsync(\n    wasmBinary,\n    wasmBinaryFile,\n    info,\n    receiveInstantiationResult\n  );\n  return {}; // no exports yet; we'll fill them in later\n}\n\n// Globals used by JS i64 conversions (see makeSetValue)\nvar tempDouble;\nvar tempI64;\n\n// include: runtime_debug.js\nfunction legacyModuleProp(prop, newName, incoming = true) {\n  if (!Object.getOwnPropertyDescriptor(Module, prop)) {\n    Object.defineProperty(Module, prop, {\n      configurable: true,\n      get() {\n        let extra = incoming\n          ? ' (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)'\n          : '';\n        abort(`\\`Module.${prop}\\` has been replaced by \\`${newName}\\`` + extra);\n      }\n    });\n  }\n}\n\nfunction ignoredModuleProp(prop) {\n  if (Object.getOwnPropertyDescriptor(Module, prop)) {\n    abort(\n      `\\`Module.${prop}\\` was supplied but \\`${prop}\\` not included in INCOMING_MODULE_JS_API`\n    );\n  }\n}\n\n// forcing the filesystem exports a few things by default\nfunction isExportedByForceFilesystem(name) {\n  return (\n    name === 'FS_createPath' ||\n    name === 'FS_createDataFile' ||\n    name === 'FS_createPreloadedFile' ||\n    name === 'FS_unlink' ||\n    name === 'addRunDependency' ||\n    // The old FS has some functionality that WasmFS lacks.\n    name === 'FS_createLazyFile' ||\n    name === 'FS_createDevice' ||\n    name === 'removeRunDependency'\n  );\n}\n\nfunction missingGlobal(sym, msg) {\n  if (typeof globalThis !== 'undefined') {\n    Object.defineProperty(globalThis, sym, {\n      configurable: true,\n      get() {\n        warnOnce(`\\`${sym}\\` is not longer defined by emscripten. ${msg}`);\n        return undefined;\n      }\n    });\n  }\n}\n\nmissingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer');\nmissingGlobal('asm', 'Please use wasmExports instead');\n\nfunction missingLibrarySymbol(sym) {\n  if (\n    typeof globalThis !== 'undefined' &&\n    !Object.getOwnPropertyDescriptor(globalThis, sym)\n  ) {\n    Object.defineProperty(globalThis, sym, {\n      configurable: true,\n      get() {\n        // Can't `abort()` here because it would break code that does runtime\n        // checks.  e.g. `if (typeof SDL === 'undefined')`.\n        var msg = `\\`${sym}\\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`;\n        // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in\n        // library.js, which means $name for a JS name with no prefix, or name\n        // for a JS name like _name.\n        var librarySymbol = sym;\n        if (!librarySymbol.startsWith('_')) {\n          librarySymbol = '$' + sym;\n        }\n        msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`;\n        if (isExportedByForceFilesystem(sym)) {\n          msg +=\n            '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you';\n        }\n        warnOnce(msg);\n        return undefined;\n      }\n    });\n  }\n  // Any symbol that is not included from the JS library is also (by definition)\n  // not exported on the Module object.\n  unexportedRuntimeSymbol(sym);\n}\n\nfunction unexportedRuntimeSymbol(sym) {\n  if (!Object.getOwnPropertyDescriptor(Module, sym)) {\n    Object.defineProperty(Module, sym, {\n      configurable: true,\n      get() {\n        var msg = `'${sym}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`;\n        if (isExportedByForceFilesystem(sym)) {\n          msg +=\n            '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you';\n        }\n        abort(msg);\n      }\n    });\n  }\n}\n\n// Used by XXXXX_DEBUG settings to output debug messages.\nfunction dbg(...args) {\n  // TODO(sbc): Make this configurable somehow.  Its not always convenient for\n  // logging to show up as warnings.\n  console.warn(...args);\n}\n// end include: runtime_debug.js\n// === Body ===\n// end include: preamble.js\n\n/** @constructor */\nfunction ExitStatus(status) {\n  this.name = 'ExitStatus';\n  this.message = `Program terminated with exit(${status})`;\n  this.status = status;\n}\n\nvar callRuntimeCallbacks = (callbacks) => {\n  while (callbacks.length > 0) {\n    // Pass the module as the first argument.\n    callbacks.shift()(Module);\n  }\n};\n\n/**\n * @param {number} ptr\n * @param {string} type\n */\nfunction getValue(ptr, type = 'i8') {\n  if (type.endsWith('*')) type = '*';\n  switch (type) {\n    case 'i1':\n      return HEAP8[ptr];\n    case 'i8':\n      return HEAP8[ptr];\n    case 'i16':\n      return HEAP16[ptr >> 1];\n    case 'i32':\n      return HEAP32[ptr >> 2];\n    case 'i64':\n      abort('to do getValue(i64) use WASM_BIGINT');\n    case 'float':\n      return HEAPF32[ptr >> 2];\n    case 'double':\n      return HEAPF64[ptr >> 3];\n    case '*':\n      return HEAPU32[ptr >> 2];\n    default:\n      abort(`invalid type for getValue: ${type}`);\n  }\n}\n\nvar noExitRuntime = Module['noExitRuntime'] || false;\n\nvar ptrToString = (ptr) => {\n  assert(typeof ptr === 'number');\n  // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned.\n  ptr >>>= 0;\n  return '0x' + ptr.toString(16).padStart(8, '0');\n};\n\n/**\n * @param {number} ptr\n * @param {number} value\n * @param {string} type\n */\nfunction setValue(ptr, value, type = 'i8') {\n  if (type.endsWith('*')) type = '*';\n  switch (type) {\n    case 'i1':\n      HEAP8[ptr] = value;\n      break;\n    case 'i8':\n      HEAP8[ptr] = value;\n      break;\n    case 'i16':\n      HEAP16[ptr >> 1] = value;\n      break;\n    case 'i32':\n      HEAP32[ptr >> 2] = value;\n      break;\n    case 'i64':\n      abort('to do setValue(i64) use WASM_BIGINT');\n    case 'float':\n      HEAPF32[ptr >> 2] = value;\n      break;\n    case 'double':\n      HEAPF64[ptr >> 3] = value;\n      break;\n    case '*':\n      HEAPU32[ptr >> 2] = value;\n      break;\n    default:\n      abort(`invalid type for setValue: ${type}`);\n  }\n}\n\nvar warnOnce = (text) => {\n  warnOnce.shown ||= {};\n  if (!warnOnce.shown[text]) {\n    warnOnce.shown[text] = 1;\n    if (ENVIRONMENT_IS_NODE) text = 'warning: ' + text;\n    err(text);\n  }\n};\n\nvar UTF8Decoder =\n  typeof TextDecoder != 'undefined' ? new TextDecoder('utf8') : undefined;\n\n/**\n * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given\n * array that contains uint8 values, returns a copy of that string as a\n * Javascript String object.\n * heapOrArray is either a regular array, or a JavaScript typed array view.\n * @param {number} idx\n * @param {number=} maxBytesToRead\n * @return {string}\n */\nvar UTF8ArrayToString = (heapOrArray, idx, maxBytesToRead) => {\n  var endIdx = idx + maxBytesToRead;\n  var endPtr = idx;\n  // TextDecoder needs to know the byte length in advance, it doesn't stop on\n  // null terminator by itself.  Also, use the length info to avoid running tiny\n  // strings through TextDecoder, since .subarray() allocates garbage.\n  // (As a tiny code save trick, compare endPtr against endIdx using a negation,\n  // so that undefined means Infinity)\n  while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr;\n\n  if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {\n    return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));\n  }\n  var str = '';\n  // If building with TextDecoder, we have already computed the string length\n  // above, so test loop end condition against that\n  while (idx < endPtr) {\n    // For UTF8 byte structure, see:\n    // http://en.wikipedia.org/wiki/UTF-8#Description\n    // https://www.ietf.org/rfc/rfc2279.txt\n    // https://tools.ietf.org/html/rfc3629\n    var u0 = heapOrArray[idx++];\n    if (!(u0 & 0x80)) {\n      str += String.fromCharCode(u0);\n      continue;\n    }\n    var u1 = heapOrArray[idx++] & 63;\n    if ((u0 & 0xe0) == 0xc0) {\n      str += String.fromCharCode(((u0 & 31) << 6) | u1);\n      continue;\n    }\n    var u2 = heapOrArray[idx++] & 63;\n    if ((u0 & 0xf0) == 0xe0) {\n      u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;\n    } else {\n      if ((u0 & 0xf8) != 0xf0)\n        warnOnce(\n          'Invalid UTF-8 leading byte ' +\n            ptrToString(u0) +\n            ' encountered when deserializing a UTF-8 string in wasm memory to a JS string!'\n        );\n      u0 =\n        ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63);\n    }\n\n    if (u0 < 0x10000) {\n      str += String.fromCharCode(u0);\n    } else {\n      var ch = u0 - 0x10000;\n      str += String.fromCharCode(0xd800 | (ch >> 10), 0xdc00 | (ch & 0x3ff));\n    }\n  }\n  return str;\n};\n\n/**\n * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the\n * emscripten HEAP, returns a copy of that string as a Javascript String object.\n *\n * @param {number} ptr\n * @param {number=} maxBytesToRead - An optional length that specifies the\n *   maximum number of bytes to read. You can omit this parameter to scan the\n *   string until the first 0 byte. If maxBytesToRead is passed, and the string\n *   at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the\n *   string will cut short at that byte index (i.e. maxBytesToRead will not\n *   produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing\n *   frequent uses of UTF8ToString() with and without maxBytesToRead may throw\n *   JS JIT optimizations off, so it is worth to consider consistently using one\n * @return {string}\n */\nvar UTF8ToString = (ptr, maxBytesToRead) => {\n  assert(\n    typeof ptr == 'number',\n    `UTF8ToString expects a number (got ${typeof ptr})`\n  );\n  return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : '';\n};\nvar ___assert_fail = (condition, filename, line, func) => {\n  abort(\n    `Assertion failed: ${UTF8ToString(condition)}, at: ` +\n      [\n        filename ? UTF8ToString(filename) : 'unknown filename',\n        line,\n        func ? UTF8ToString(func) : 'unknown function'\n      ]\n  );\n};\n\nvar PATH = {\n  isAbs: (path) => path.charAt(0) === '/',\n  splitPath: (filename) => {\n    var splitPathRe =\n      /^(\\/?|)([\\s\\S]*?)((?:\\.{1,2}|[^\\/]+?|)(\\.[^.\\/]*|))(?:[\\/]*)$/;\n    return splitPathRe.exec(filename).slice(1);\n  },\n  normalizeArray: (parts, allowAboveRoot) => {\n    // if the path tries to go above the root, `up` ends up > 0\n    var up = 0;\n    for (var i = parts.length - 1; i >= 0; i--) {\n      var last = parts[i];\n      if (last === '.') {\n        parts.splice(i, 1);\n      } else if (last === '..') {\n        parts.splice(i, 1);\n        up++;\n      } else if (up) {\n        parts.splice(i, 1);\n        up--;\n      }\n    }\n    // if the path is allowed to go above the root, restore leading ..s\n    if (allowAboveRoot) {\n      for (; up; up--) {\n        parts.unshift('..');\n      }\n    }\n    return parts;\n  },\n  normalize: (path) => {\n    var isAbsolute = PATH.isAbs(path),\n      trailingSlash = path.substr(-1) === '/';\n    // Normalize the path\n    path = PATH.normalizeArray(\n      path.split('/').filter((p) => !!p),\n      !isAbsolute\n    ).join('/');\n    if (!path && !isAbsolute) {\n      path = '.';\n    }\n    if (path && trailingSlash) {\n      path += '/';\n    }\n    return (isAbsolute ? '/' : '') + path;\n  },\n  dirname: (path) => {\n    var result = PATH.splitPath(path),\n      root = result[0],\n      dir = result[1];\n    if (!root && !dir) {\n      // No dirname whatsoever\n      return '.';\n    }\n    if (dir) {\n      // It has a dirname, strip trailing slash\n      dir = dir.substr(0, dir.length - 1);\n    }\n    return root + dir;\n  },\n  basename: (path) => {\n    // EMSCRIPTEN return '/'' for '/', not an empty string\n    if (path === '/') return '/';\n    path = PATH.normalize(path);\n    path = path.replace(/\\/$/, '');\n    var lastSlash = path.lastIndexOf('/');\n    if (lastSlash === -1) return path;\n    return path.substr(lastSlash + 1);\n  },\n  join: (...paths) => PATH.normalize(paths.join('/')),\n  join2: (l, r) => PATH.normalize(l + '/' + r)\n};\n\nvar initRandomFill = () => {\n  if (\n    typeof crypto == 'object' &&\n    typeof crypto['getRandomValues'] == 'function'\n  ) {\n    // for modern web browsers\n    return (view) => crypto.getRandomValues(view);\n  } else if (ENVIRONMENT_IS_NODE) {\n    // for nodejs with or without crypto support included\n    try {\n      var crypto_module = require('crypto');\n      var randomFillSync = crypto_module['randomFillSync'];\n      if (randomFillSync) {\n        // nodejs with LTS crypto support\n        return (view) => crypto_module['randomFillSync'](view);\n      }\n      // very old nodejs with the original crypto API\n      var randomBytes = crypto_module['randomBytes'];\n      return (view) => (\n        view.set(randomBytes(view.byteLength)),\n        // Return the original view to match modern native implementations.\n        view\n      );\n    } catch (e) {\n      // nodejs doesn't have crypto support\n    }\n  }\n  // we couldn't find a proper implementation, as Math.random() is not suitable for /dev/random, see emscripten-core/emscripten/pull/7096\n  abort(\n    'no cryptographic support found for randomDevice. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: (array) => { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };'\n  );\n};\nvar randomFill = (view) => {\n  // Lazily init on the first invocation.\n  return (randomFill = initRandomFill())(view);\n};\n\nvar PATH_FS = {\n  resolve: (...args) => {\n    var resolvedPath = '',\n      resolvedAbsolute = false;\n    for (var i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {\n      var path = i >= 0 ? args[i] : FS.cwd();\n      // Skip empty and invalid entries\n      if (typeof path != 'string') {\n        throw new TypeError('Arguments to path.resolve must be strings');\n      } else if (!path) {\n        return ''; // an invalid portion invalidates the whole thing\n      }\n      resolvedPath = path + '/' + resolvedPath;\n      resolvedAbsolute = PATH.isAbs(path);\n    }\n    // At this point the path should be resolved to a full absolute path, but\n    // handle relative paths to be safe (might happen when process.cwd() fails)\n    resolvedPath = PATH.normalizeArray(\n      resolvedPath.split('/').filter((p) => !!p),\n      !resolvedAbsolute\n    ).join('/');\n    return (resolvedAbsolute ? '/' : '') + resolvedPath || '.';\n  },\n  relative: (from, to) => {\n    from = PATH_FS.resolve(from).substr(1);\n    to = PATH_FS.resolve(to).substr(1);\n    function trim(arr) {\n      var start = 0;\n      for (; start < arr.length; start++) {\n        if (arr[start] !== '') break;\n      }\n      var end = arr.length - 1;\n      for (; end >= 0; end--) {\n        if (arr[end] !== '') break;\n      }\n      if (start > end) return [];\n      return arr.slice(start, end - start + 1);\n    }\n    var fromParts = trim(from.split('/'));\n    var toParts = trim(to.split('/'));\n    var length = Math.min(fromParts.length, toParts.length);\n    var samePartsLength = length;\n    for (var i = 0; i < length; i++) {\n      if (fromParts[i] !== toParts[i]) {\n        samePartsLength = i;\n        break;\n      }\n    }\n    var outputParts = [];\n    for (var i = samePartsLength; i < fromParts.length; i++) {\n      outputParts.push('..');\n    }\n    outputParts = outputParts.concat(toParts.slice(samePartsLength));\n    return outputParts.join('/');\n  }\n};\n\nvar FS_stdin_getChar_buffer = [];\n\nvar lengthBytesUTF8 = (str) => {\n  var len = 0;\n  for (var i = 0; i < str.length; ++i) {\n    // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code\n    // unit, not a Unicode code point of the character! So decode\n    // UTF16->UTF32->UTF8.\n    // See http://unicode.org/faq/utf_bom.html#utf16-3\n    var c = str.charCodeAt(i); // possibly a lead surrogate\n    if (c <= 0x7f) {\n      len++;\n    } else if (c <= 0x7ff) {\n      len += 2;\n    } else if (c >= 0xd800 && c <= 0xdfff) {\n      len += 4;\n      ++i;\n    } else {\n      len += 3;\n    }\n  }\n  return len;\n};\n\nvar stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => {\n  assert(\n    typeof str === 'string',\n    `stringToUTF8Array expects a string (got ${typeof str})`\n  );\n  // Parameter maxBytesToWrite is not optional. Negative values, 0, null,\n  // undefined and false each don't write out any bytes.\n  if (!(maxBytesToWrite > 0)) return 0;\n\n  var startIdx = outIdx;\n  var endIdx = outIdx + maxBytesToWrite - 1; // -1 for string null terminator.\n  for (var i = 0; i < str.length; ++i) {\n    // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code\n    // unit, not a Unicode code point of the character! So decode\n    // UTF16->UTF32->UTF8.\n    // See http://unicode.org/faq/utf_bom.html#utf16-3\n    // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description\n    // and https://www.ietf.org/rfc/rfc2279.txt\n    // and https://tools.ietf.org/html/rfc3629\n    var u = str.charCodeAt(i); // possibly a lead surrogate\n    if (u >= 0xd800 && u <= 0xdfff) {\n      var u1 = str.charCodeAt(++i);\n      u = (0x10000 + ((u & 0x3ff) << 10)) | (u1 & 0x3ff);\n    }\n    if (u <= 0x7f) {\n      if (outIdx >= endIdx) break;\n      heap[outIdx++] = u;\n    } else if (u <= 0x7ff) {\n      if (outIdx + 1 >= endIdx) break;\n      heap[outIdx++] = 0xc0 | (u >> 6);\n      heap[outIdx++] = 0x80 | (u & 63);\n    } else if (u <= 0xffff) {\n      if (outIdx + 2 >= endIdx) break;\n      heap[outIdx++] = 0xe0 | (u >> 12);\n      heap[outIdx++] = 0x80 | ((u >> 6) & 63);\n      heap[outIdx++] = 0x80 | (u & 63);\n    } else {\n      if (outIdx + 3 >= endIdx) break;\n      if (u > 0x10ffff)\n        warnOnce(\n          'Invalid Unicode code point ' +\n            ptrToString(u) +\n            ' encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF).'\n        );\n      heap[outIdx++] = 0xf0 | (u >> 18);\n      heap[outIdx++] = 0x80 | ((u >> 12) & 63);\n      heap[outIdx++] = 0x80 | ((u >> 6) & 63);\n      heap[outIdx++] = 0x80 | (u & 63);\n    }\n  }\n  // Null-terminate the pointer to the buffer.\n  heap[outIdx] = 0;\n  return outIdx - startIdx;\n};\n/** @type {function(string, boolean=, number=)} */\nfunction intArrayFromString(stringy, dontAddNull, length) {\n  var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1;\n  var u8array = new Array(len);\n  var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length);\n  if (dontAddNull) u8array.length = numBytesWritten;\n  return u8array;\n}\nvar FS_stdin_getChar = () => {\n  if (!FS_stdin_getChar_buffer.length) {\n    var result = null;\n    if (ENVIRONMENT_IS_NODE) {\n      // we will read data by chunks of BUFSIZE\n      var BUFSIZE = 256;\n      var buf = Buffer.alloc(BUFSIZE);\n      var bytesRead = 0;\n\n      // For some reason we must suppress a closure warning here, even though\n      // fd definitely exists on process.stdin, and is even the proper way to\n      // get the fd of stdin,\n      // https://github.com/nodejs/help/issues/2136#issuecomment-523649904\n      // This started to happen after moving this logic out of library_tty.js,\n      // so it is related to the surrounding code in some unclear manner.\n      /** @suppress {missingProperties} */\n      var fd = process.stdin.fd;\n\n      try {\n        bytesRead = fs.readSync(fd, buf);\n      } catch (e) {\n        // Cross-platform differences: on Windows, reading EOF throws an exception, but on other OSes,\n        // reading EOF returns 0. Uniformize behavior by treating the EOF exception to return 0.\n        if (e.toString().includes('EOF')) bytesRead = 0;\n        else throw e;\n      }\n\n      if (bytesRead > 0) {\n        result = buf.slice(0, bytesRead).toString('utf-8');\n      } else {\n        result = null;\n      }\n    } else if (\n      typeof window != 'undefined' &&\n      typeof window.prompt == 'function'\n    ) {\n      // Browser.\n      result = window.prompt('Input: '); // returns null on cancel\n      if (result !== null) {\n        result += '\\n';\n      }\n    } else if (typeof readline == 'function') {\n      // Command line.\n      result = readline();\n      if (result !== null) {\n        result += '\\n';\n      }\n    }\n    if (!result) {\n      return null;\n    }\n    FS_stdin_getChar_buffer = intArrayFromString(result, true);\n  }\n  return FS_stdin_getChar_buffer.shift();\n};\nvar TTY = {\n  ttys: [],\n  init() {\n    // https://github.com/emscripten-core/emscripten/pull/1555\n    // if (ENVIRONMENT_IS_NODE) {\n    //   // currently, FS.init does not distinguish if process.stdin is a file or TTY\n    //   // device, it always assumes it's a TTY device. because of this, we're forcing\n    //   // process.stdin to UTF8 encoding to at least make stdin reading compatible\n    //   // with text files until FS.init can be refactored.\n    //   process.stdin.setEncoding('utf8');\n    // }\n  },\n  shutdown() {\n    // https://github.com/emscripten-core/emscripten/pull/1555\n    // if (ENVIRONMENT_IS_NODE) {\n    //   // inolen: any idea as to why node -e 'process.stdin.read()' wouldn't exit immediately (with process.stdin being a tty)?\n    //   // isaacs: because now it's reading from the stream, you've expressed interest in it, so that read() kicks off a _read() which creates a ReadReq operation\n    //   // inolen: I thought read() in that case was a synchronous operation that just grabbed some amount of buffered data if it exists?\n    //   // isaacs: it is. but it also triggers a _read() call, which calls readStart() on the handle\n    //   // isaacs: do process.stdin.pause() and i'd think it'd probably close the pending call\n    //   process.stdin.pause();\n    // }\n  },\n  register(dev, ops) {\n    TTY.ttys[dev] = { input: [], output: [], ops: ops };\n    FS.registerDevice(dev, TTY.stream_ops);\n  },\n  stream_ops: {\n    open(stream) {\n      var tty = TTY.ttys[stream.node.rdev];\n      if (!tty) {\n        throw new FS.ErrnoError(43);\n      }\n      stream.tty = tty;\n      stream.seekable = false;\n    },\n    close(stream) {\n      // flush any pending line data\n      stream.tty.ops.fsync(stream.tty);\n    },\n    fsync(stream) {\n      stream.tty.ops.fsync(stream.tty);\n    },\n    read(stream, buffer, offset, length, pos /* ignored */) {\n      if (!stream.tty || !stream.tty.ops.get_char) {\n        throw new FS.ErrnoError(60);\n      }\n      var bytesRead = 0;\n      for (var i = 0; i < length; i++) {\n        var result;\n        try {\n          result = stream.tty.ops.get_char(stream.tty);\n        } catch (e) {\n          throw new FS.ErrnoError(29);\n        }\n        if (result === undefined && bytesRead === 0) {\n          throw new FS.ErrnoError(6);\n        }\n        if (result === null || result === undefined) break;\n        bytesRead++;\n        buffer[offset + i] = result;\n      }\n      if (bytesRead) {\n        stream.node.timestamp = Date.now();\n      }\n      return bytesRead;\n    },\n    write(stream, buffer, offset, length, pos) {\n      if (!stream.tty || !stream.tty.ops.put_char) {\n        throw new FS.ErrnoError(60);\n      }\n      try {\n        for (var i = 0; i < length; i++) {\n          stream.tty.ops.put_char(stream.tty, buffer[offset + i]);\n        }\n      } catch (e) {\n        throw new FS.ErrnoError(29);\n      }\n      if (length) {\n        stream.node.timestamp = Date.now();\n      }\n      return i;\n    }\n  },\n  default_tty_ops: {\n    get_char(tty) {\n      return FS_stdin_getChar();\n    },\n    put_char(tty, val) {\n      if (val === null || val === 10) {\n        out(UTF8ArrayToString(tty.output, 0));\n        tty.output = [];\n      } else {\n        if (val != 0) tty.output.push(val); // val == 0 would cut text output off in the middle.\n      }\n    },\n    fsync(tty) {\n      if (tty.output && tty.output.length > 0) {\n        out(UTF8ArrayToString(tty.output, 0));\n        tty.output = [];\n      }\n    },\n    ioctl_tcgets(tty) {\n      // typical setting\n      return {\n        c_iflag: 25856,\n        c_oflag: 5,\n        c_cflag: 191,\n        c_lflag: 35387,\n        c_cc: [\n          0x03, 0x1c, 0x7f, 0x15, 0x04, 0x00, 0x01, 0x00, 0x11, 0x13, 0x1a,\n          0x00, 0x12, 0x0f, 0x17, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00\n        ]\n      };\n    },\n    ioctl_tcsets(tty, optional_actions, data) {\n      // currently just ignore\n      return 0;\n    },\n    ioctl_tiocgwinsz(tty) {\n      return [24, 80];\n    }\n  },\n  default_tty1_ops: {\n    put_char(tty, val) {\n      if (val === null || val === 10) {\n        err(UTF8ArrayToString(tty.output, 0));\n        tty.output = [];\n      } else {\n        if (val != 0) tty.output.push(val);\n      }\n    },\n    fsync(tty) {\n      if (tty.output && tty.output.length > 0) {\n        err(UTF8ArrayToString(tty.output, 0));\n        tty.output = [];\n      }\n    }\n  }\n};\n\nvar zeroMemory = (address, size) => {\n  HEAPU8.fill(0, address, address + size);\n  return address;\n};\n\nvar alignMemory = (size, alignment) => {\n  assert(alignment, 'alignment argument is required');\n  return Math.ceil(size / alignment) * alignment;\n};\nvar mmapAlloc = (size) => {\n  abort(\n    'internal error: mmapAlloc called but `emscripten_builtin_memalign` native symbol not exported'\n  );\n};\nvar MEMFS = {\n  ops_table: null,\n  mount(mount) {\n    return MEMFS.createNode(null, '/', 16384 | 511 /* 0777 */, 0);\n  },\n  createNode(parent, name, mode, dev) {\n    if (FS.isBlkdev(mode) || FS.isFIFO(mode)) {\n      // no supported\n      throw new FS.ErrnoError(63);\n    }\n    MEMFS.ops_table ||= {\n      dir: {\n        node: {\n          getattr: MEMFS.node_ops.getattr,\n          setattr: MEMFS.node_ops.setattr,\n          lookup: MEMFS.node_ops.lookup,\n          mknod: MEMFS.node_ops.mknod,\n          rename: MEMFS.node_ops.rename,\n          unlink: MEMFS.node_ops.unlink,\n          rmdir: MEMFS.node_ops.rmdir,\n          readdir: MEMFS.node_ops.readdir,\n          symlink: MEMFS.node_ops.symlink\n        },\n        stream: {\n          llseek: MEMFS.stream_ops.llseek\n        }\n      },\n      file: {\n        node: {\n          getattr: MEMFS.node_ops.getattr,\n          setattr: MEMFS.node_ops.setattr\n        },\n        stream: {\n          llseek: MEMFS.stream_ops.llseek,\n          read: MEMFS.stream_ops.read,\n          write: MEMFS.stream_ops.write,\n          allocate: MEMFS.stream_ops.allocate,\n          mmap: MEMFS.stream_ops.mmap,\n          msync: MEMFS.stream_ops.msync\n        }\n      },\n      link: {\n        node: {\n          getattr: MEMFS.node_ops.getattr,\n          setattr: MEMFS.node_ops.setattr,\n          readlink: MEMFS.node_ops.readlink\n        },\n        stream: {}\n      },\n      chrdev: {\n        node: {\n          getattr: MEMFS.node_ops.getattr,\n          setattr: MEMFS.node_ops.setattr\n        },\n        stream: FS.chrdev_stream_ops\n      }\n    };\n    var node = FS.createNode(parent, name, mode, dev);\n    if (FS.isDir(node.mode)) {\n      node.node_ops = MEMFS.ops_table.dir.node;\n      node.stream_ops = MEMFS.ops_table.dir.stream;\n      node.contents = {};\n    } else if (FS.isFile(node.mode)) {\n      node.node_ops = MEMFS.ops_table.file.node;\n      node.stream_ops = MEMFS.ops_table.file.stream;\n      node.usedBytes = 0; // The actual number of bytes used in the typed array, as opposed to contents.length which gives the whole capacity.\n      // When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred\n      // for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size\n      // penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme.\n      node.contents = null;\n    } else if (FS.isLink(node.mode)) {\n      node.node_ops = MEMFS.ops_table.link.node;\n      node.stream_ops = MEMFS.ops_table.link.stream;\n    } else if (FS.isChrdev(node.mode)) {\n      node.node_ops = MEMFS.ops_table.chrdev.node;\n      node.stream_ops = MEMFS.ops_table.chrdev.stream;\n    }\n    node.timestamp = Date.now();\n    // add the new node to the parent\n    if (parent) {\n      parent.contents[name] = node;\n      parent.timestamp = node.timestamp;\n    }\n    return node;\n  },\n  getFileDataAsTypedArray(node) {\n    if (!node.contents) return new Uint8Array(0);\n    if (node.contents.subarray)\n      return node.contents.subarray(0, node.usedBytes); // Make sure to not return excess unused bytes.\n    return new Uint8Array(node.contents);\n  },\n  expandFileStorage(node, newCapacity) {\n    var prevCapacity = node.contents ? node.contents.length : 0;\n    if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough.\n    // Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity.\n    // For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to\n    // avoid overshooting the allocation cap by a very large margin.\n    var CAPACITY_DOUBLING_MAX = 1024 * 1024;\n    newCapacity = Math.max(\n      newCapacity,\n      (prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2.0 : 1.125)) >>>\n        0\n    );\n    if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); // At minimum allocate 256b for each file when expanding.\n    var oldContents = node.contents;\n    node.contents = new Uint8Array(newCapacity); // Allocate new storage.\n    if (node.usedBytes > 0)\n      node.contents.set(oldContents.subarray(0, node.usedBytes), 0); // Copy old data over to the new storage.\n  },\n  resizeFileStorage(node, newSize) {\n    if (node.usedBytes == newSize) return;\n    if (newSize == 0) {\n      node.contents = null; // Fully decommit when requesting a resize to zero.\n      node.usedBytes = 0;\n    } else {\n      var oldContents = node.contents;\n      node.contents = new Uint8Array(newSize); // Allocate new storage.\n      if (oldContents) {\n        node.contents.set(\n          oldContents.subarray(0, Math.min(newSize, node.usedBytes))\n        ); // Copy old data over to the new storage.\n      }\n      node.usedBytes = newSize;\n    }\n  },\n  node_ops: {\n    getattr(node) {\n      var attr = {};\n      // device numbers reuse inode numbers.\n      attr.dev = FS.isChrdev(node.mode) ? node.id : 1;\n      attr.ino = node.id;\n      attr.mode = node.mode;\n      attr.nlink = 1;\n      attr.uid = 0;\n      attr.gid = 0;\n      attr.rdev = node.rdev;\n      if (FS.isDir(node.mode)) {\n        attr.size = 4096;\n      } else if (FS.isFile(node.mode)) {\n        attr.size = node.usedBytes;\n      } else if (FS.isLink(node.mode)) {\n        attr.size = node.link.length;\n      } else {\n        attr.size = 0;\n      }\n      attr.atime = new Date(node.timestamp);\n      attr.mtime = new Date(node.timestamp);\n      attr.ctime = new Date(node.timestamp);\n      // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize),\n      //       but this is not required by the standard.\n      attr.blksize = 4096;\n      attr.blocks = Math.ceil(attr.size / attr.blksize);\n      return attr;\n    },\n    setattr(node, attr) {\n      if (attr.mode !== undefined) {\n        node.mode = attr.mode;\n      }\n      if (attr.timestamp !== undefined) {\n        node.timestamp = attr.timestamp;\n      }\n      if (attr.size !== undefined) {\n        MEMFS.resizeFileStorage(node, attr.size);\n      }\n    },\n    lookup(parent, name) {\n      throw FS.genericErrors[44];\n    },\n    mknod(parent, name, mode, dev) {\n      return MEMFS.createNode(parent, name, mode, dev);\n    },\n    rename(old_node, new_dir, new_name) {\n      // if we're overwriting a directory at new_name, make sure it's empty.\n      if (FS.isDir(old_node.mode)) {\n        var new_node;\n        try {\n          new_node = FS.lookupNode(new_dir, new_name);\n        } catch (e) {}\n        if (new_node) {\n          for (var i in new_node.contents) {\n            throw new FS.ErrnoError(55);\n          }\n        }\n      }\n      // do the internal rewiring\n      delete old_node.parent.contents[old_node.name];\n      old_node.parent.timestamp = Date.now();\n      old_node.name = new_name;\n      new_dir.contents[new_name] = old_node;\n      new_dir.timestamp = old_node.parent.timestamp;\n      old_node.parent = new_dir;\n    },\n    unlink(parent, name) {\n      delete parent.contents[name];\n      parent.timestamp = Date.now();\n    },\n    rmdir(parent, name) {\n      var node = FS.lookupNode(parent, name);\n      for (var i in node.contents) {\n        throw new FS.ErrnoError(55);\n      }\n      delete parent.contents[name];\n      parent.timestamp = Date.now();\n    },\n    readdir(node) {\n      var entries = ['.', '..'];\n      for (var key of Object.keys(node.contents)) {\n        entries.push(key);\n      }\n      return entries;\n    },\n    symlink(parent, newname, oldpath) {\n      var node = MEMFS.createNode(parent, newname, 511 /* 0777 */ | 40960, 0);\n      node.link = oldpath;\n      return node;\n    },\n    readlink(node) {\n      if (!FS.isLink(node.mode)) {\n        throw new FS.ErrnoError(28);\n      }\n      return node.link;\n    }\n  },\n  stream_ops: {\n    read(stream, buffer, offset, length, position) {\n      var contents = stream.node.contents;\n      if (position >= stream.node.usedBytes) return 0;\n      var size = Math.min(stream.node.usedBytes - position, length);\n      assert(size >= 0);\n      if (size > 8 && contents.subarray) {\n        // non-trivial, and typed array\n        buffer.set(contents.subarray(position, position + size), offset);\n      } else {\n        for (var i = 0; i < size; i++)\n          buffer[offset + i] = contents[position + i];\n      }\n      return size;\n    },\n    write(stream, buffer, offset, length, position, canOwn) {\n      // The data buffer should be a typed array view\n      assert(!(buffer instanceof ArrayBuffer));\n      // If the buffer is located in main memory (HEAP), and if\n      // memory can grow, we can't hold on to references of the\n      // memory buffer, as they may get invalidated. That means we\n      // need to do copy its contents.\n      if (buffer.buffer === HEAP8.buffer) {\n        canOwn = false;\n      }\n\n      if (!length) return 0;\n      var node = stream.node;\n      node.timestamp = Date.now();\n\n      if (buffer.subarray && (!node.contents || node.contents.subarray)) {\n        // This write is from a typed array to a typed array?\n        if (canOwn) {\n          assert(\n            position === 0,\n            'canOwn must imply no weird position inside the file'\n          );\n          node.contents = buffer.subarray(offset, offset + length);\n          node.usedBytes = length;\n          return length;\n        } else if (node.usedBytes === 0 && position === 0) {\n          // If this is a simple first write to an empty file, do a fast set since we don't need to care about old data.\n          node.contents = buffer.slice(offset, offset + length);\n          node.usedBytes = length;\n          return length;\n        } else if (position + length <= node.usedBytes) {\n          // Writing to an already allocated and used subrange of the file?\n          node.contents.set(buffer.subarray(offset, offset + length), position);\n          return length;\n        }\n      }\n\n      // Appending to an existing file and we need to reallocate, or source data did not come as a typed array.\n      MEMFS.expandFileStorage(node, position + length);\n      if (node.contents.subarray && buffer.subarray) {\n        // Use typed array write which is available.\n        node.contents.set(buffer.subarray(offset, offset + length), position);\n      } else {\n        for (var i = 0; i < length; i++) {\n          node.contents[position + i] = buffer[offset + i]; // Or fall back to manual write if not.\n        }\n      }\n      node.usedBytes = Math.max(node.usedBytes, position + length);\n      return length;\n    },\n    llseek(stream, offset, whence) {\n      var position = offset;\n      if (whence === 1) {\n        position += stream.position;\n      } else if (whence === 2) {\n        if (FS.isFile(stream.node.mode)) {\n          position += stream.node.usedBytes;\n        }\n      }\n      if (position < 0) {\n        throw new FS.ErrnoError(28);\n      }\n      return position;\n    },\n    allocate(stream, offset, length) {\n      MEMFS.expandFileStorage(stream.node, offset + length);\n      stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length);\n    },\n    mmap(stream, length, position, prot, flags) {\n      if (!FS.isFile(stream.node.mode)) {\n        throw new FS.ErrnoError(43);\n      }\n      var ptr;\n      var allocated;\n      var contents = stream.node.contents;\n      // Only make a new copy when MAP_PRIVATE is specified.\n      if (!(flags & 2) && contents.buffer === HEAP8.buffer) {\n        // We can't emulate MAP_SHARED when the file is not backed by the\n        // buffer we're mapping to (e.g. the HEAP buffer).\n        allocated = false;\n        ptr = contents.byteOffset;\n      } else {\n        // Try to avoid unnecessary slices.\n        if (position > 0 || position + length < contents.length) {\n          if (contents.subarray) {\n            contents = contents.subarray(position, position + length);\n          } else {\n            contents = Array.prototype.slice.call(\n              contents,\n              position,\n              position + length\n            );\n          }\n        }\n        allocated = true;\n        ptr = mmapAlloc(length);\n        if (!ptr) {\n          throw new FS.ErrnoError(48);\n        }\n        HEAP8.set(contents, ptr);\n      }\n      return { ptr, allocated };\n    },\n    msync(stream, buffer, offset, length, mmapFlags) {\n      MEMFS.stream_ops.write(stream, buffer, 0, length, offset, false);\n      // should we check if bytesWritten and length are the same?\n      return 0;\n    }\n  }\n};\n\n/** @param {boolean=} noRunDep */\nvar asyncLoad = (url, onload, onerror, noRunDep) => {\n  var dep = !noRunDep ? getUniqueRunDependency(`al ${url}`) : '';\n  readAsync(\n    url,\n    (arrayBuffer) => {\n      assert(\n        arrayBuffer,\n        `Loading data file \"${url}\" failed (no arrayBuffer).`\n      );\n      onload(new Uint8Array(arrayBuffer));\n      if (dep) removeRunDependency(dep);\n    },\n    (event) => {\n      if (onerror) {\n        onerror();\n      } else {\n        throw `Loading data file \"${url}\" failed.`;\n      }\n    }\n  );\n  if (dep) addRunDependency(dep);\n};\n\nvar FS_createDataFile = (parent, name, fileData, canRead, canWrite, canOwn) => {\n  FS.createDataFile(parent, name, fileData, canRead, canWrite, canOwn);\n};\n\nvar preloadPlugins = Module['preloadPlugins'] || [];\nvar FS_handledByPreloadPlugin = (byteArray, fullname, finish, onerror) => {\n  // Ensure plugins are ready.\n  if (typeof Browser != 'undefined') Browser.init();\n\n  var handled = false;\n  preloadPlugins.forEach((plugin) => {\n    if (handled) return;\n    if (plugin['canHandle'](fullname)) {\n      plugin['handle'](byteArray, fullname, finish, onerror);\n      handled = true;\n    }\n  });\n  return handled;\n};\nvar FS_createPreloadedFile = (\n  parent,\n  name,\n  url,\n  canRead,\n  canWrite,\n  onload,\n  onerror,\n  dontCreateFile,\n  canOwn,\n  preFinish\n) => {\n  // TODO we should allow people to just pass in a complete filename instead\n  // of parent and name being that we just join them anyways\n  var fullname = name ? PATH_FS.resolve(PATH.join2(parent, name)) : parent;\n  var dep = getUniqueRunDependency(`cp ${fullname}`); // might have several active requests for the same fullname\n  function processData(byteArray) {\n    function finish(byteArray) {\n      preFinish?.();\n      if (!dontCreateFile) {\n        FS_createDataFile(parent, name, byteArray, canRead, canWrite, canOwn);\n      }\n      onload?.();\n      removeRunDependency(dep);\n    }\n    if (\n      FS_handledByPreloadPlugin(byteArray, fullname, finish, () => {\n        onerror?.();\n        removeRunDependency(dep);\n      })\n    ) {\n      return;\n    }\n    finish(byteArray);\n  }\n  addRunDependency(dep);\n  if (typeof url == 'string') {\n    asyncLoad(url, processData, onerror);\n  } else {\n    processData(url);\n  }\n};\n\nvar FS_modeStringToFlags = (str) => {\n  var flagModes = {\n    r: 0,\n    'r+': 2,\n    w: 512 | 64 | 1,\n    'w+': 512 | 64 | 2,\n    a: 1024 | 64 | 1,\n    'a+': 1024 | 64 | 2\n  };\n  var flags = flagModes[str];\n  if (typeof flags == 'undefined') {\n    throw new Error(`Unknown file open mode: ${str}`);\n  }\n  return flags;\n};\n\nvar FS_getMode = (canRead, canWrite) => {\n  var mode = 0;\n  if (canRead) mode |= 292 | 73;\n  if (canWrite) mode |= 146;\n  return mode;\n};\n\nvar ERRNO_MESSAGES = {\n  0: 'Success',\n  1: 'Arg list too long',\n  2: 'Permission denied',\n  3: 'Address already in use',\n  4: 'Address not available',\n  5: 'Address family not supported by protocol family',\n  6: 'No more processes',\n  7: 'Socket already connected',\n  8: 'Bad file number',\n  9: 'Trying to read unreadable message',\n  10: 'Mount device busy',\n  11: 'Operation canceled',\n  12: 'No children',\n  13: 'Connection aborted',\n  14: 'Connection refused',\n  15: 'Connection reset by peer',\n  16: 'File locking deadlock error',\n  17: 'Destination address required',\n  18: 'Math arg out of domain of func',\n  19: 'Quota exceeded',\n  20: 'File exists',\n  21: 'Bad address',\n  22: 'File too large',\n  23: 'Host is unreachable',\n  24: 'Identifier removed',\n  25: 'Illegal byte sequence',\n  26: 'Connection already in progress',\n  27: 'Interrupted system call',\n  28: 'Invalid argument',\n  29: 'I/O error',\n  30: 'Socket is already connected',\n  31: 'Is a directory',\n  32: 'Too many symbolic links',\n  33: 'Too many open files',\n  34: 'Too many links',\n  35: 'Message too long',\n  36: 'Multihop attempted',\n  37: 'File or path name too long',\n  38: 'Network interface is not configured',\n  39: 'Connection reset by network',\n  40: 'Network is unreachable',\n  41: 'Too many open files in system',\n  42: 'No buffer space available',\n  43: 'No such device',\n  44: 'No such file or directory',\n  45: 'Exec format error',\n  46: 'No record locks available',\n  47: 'The link has been severed',\n  48: 'Not enough core',\n  49: 'No message of desired type',\n  50: 'Protocol not available',\n  51: 'No space left on device',\n  52: 'Function not implemented',\n  53: 'Socket is not connected',\n  54: 'Not a directory',\n  55: 'Directory not empty',\n  56: 'State not recoverable',\n  57: 'Socket operation on non-socket',\n  59: 'Not a typewriter',\n  60: 'No such device or address',\n  61: 'Value too large for defined data type',\n  62: 'Previous owner died',\n  63: 'Not super-user',\n  64: 'Broken pipe',\n  65: 'Protocol error',\n  66: 'Unknown protocol',\n  67: 'Protocol wrong type for socket',\n  68: 'Math result not representable',\n  69: 'Read only file system',\n  70: 'Illegal seek',\n  71: 'No such process',\n  72: 'Stale file handle',\n  73: 'Connection timed out',\n  74: 'Text file busy',\n  75: 'Cross-device link',\n  100: 'Device not a stream',\n  101: 'Bad font file fmt',\n  102: 'Invalid slot',\n  103: 'Invalid request code',\n  104: 'No anode',\n  105: 'Block device required',\n  106: 'Channel number out of range',\n  107: 'Level 3 halted',\n  108: 'Level 3 reset',\n  109: 'Link number out of range',\n  110: 'Protocol driver not attached',\n  111: 'No CSI structure available',\n  112: 'Level 2 halted',\n  113: 'Invalid exchange',\n  114: 'Invalid request descriptor',\n  115: 'Exchange full',\n  116: 'No data (for no delay io)',\n  117: 'Timer expired',\n  118: 'Out of streams resources',\n  119: 'Machine is not on the network',\n  120: 'Package not installed',\n  121: 'The object is remote',\n  122: 'Advertise error',\n  123: 'Srmount error',\n  124: 'Communication error on send',\n  125: 'Cross mount point (not really error)',\n  126: 'Given log. name not unique',\n  127: 'f.d. invalid for this operation',\n  128: 'Remote address changed',\n  129: 'Can   access a needed shared lib',\n  130: 'Accessing a corrupted shared lib',\n  131: '.lib section in a.out corrupted',\n  132: 'Attempting to link in too many libs',\n  133: 'Attempting to exec a shared library',\n  135: 'Streams pipe error',\n  136: 'Too many users',\n  137: 'Socket type not supported',\n  138: 'Not supported',\n  139: 'Protocol family not supported',\n  140: \"Can't send after socket shutdown\",\n  141: 'Too many references',\n  142: 'Host is down',\n  148: 'No medium (in tape drive)',\n  156: 'Level 2 not synchronized'\n};\n\nvar ERRNO_CODES = {\n  EPERM: 63,\n  ENOENT: 44,\n  ESRCH: 71,\n  EINTR: 27,\n  EIO: 29,\n  ENXIO: 60,\n  E2BIG: 1,\n  ENOEXEC: 45,\n  EBADF: 8,\n  ECHILD: 12,\n  EAGAIN: 6,\n  EWOULDBLOCK: 6,\n  ENOMEM: 48,\n  EACCES: 2,\n  EFAULT: 21,\n  ENOTBLK: 105,\n  EBUSY: 10,\n  EEXIST: 20,\n  EXDEV: 75,\n  ENODEV: 43,\n  ENOTDIR: 54,\n  EISDIR: 31,\n  EINVAL: 28,\n  ENFILE: 41,\n  EMFILE: 33,\n  ENOTTY: 59,\n  ETXTBSY: 74,\n  EFBIG: 22,\n  ENOSPC: 51,\n  ESPIPE: 70,\n  EROFS: 69,\n  EMLINK: 34,\n  EPIPE: 64,\n  EDOM: 18,\n  ERANGE: 68,\n  ENOMSG: 49,\n  EIDRM: 24,\n  ECHRNG: 106,\n  EL2NSYNC: 156,\n  EL3HLT: 107,\n  EL3RST: 108,\n  ELNRNG: 109,\n  EUNATCH: 110,\n  ENOCSI: 111,\n  EL2HLT: 112,\n  EDEADLK: 16,\n  ENOLCK: 46,\n  EBADE: 113,\n  EBADR: 114,\n  EXFULL: 115,\n  ENOANO: 104,\n  EBADRQC: 103,\n  EBADSLT: 102,\n  EDEADLOCK: 16,\n  EBFONT: 101,\n  ENOSTR: 100,\n  ENODATA: 116,\n  ETIME: 117,\n  ENOSR: 118,\n  ENONET: 119,\n  ENOPKG: 120,\n  EREMOTE: 121,\n  ENOLINK: 47,\n  EADV: 122,\n  ESRMNT: 123,\n  ECOMM: 124,\n  EPROTO: 65,\n  EMULTIHOP: 36,\n  EDOTDOT: 125,\n  EBADMSG: 9,\n  ENOTUNIQ: 126,\n  EBADFD: 127,\n  EREMCHG: 128,\n  ELIBACC: 129,\n  ELIBBAD: 130,\n  ELIBSCN: 131,\n  ELIBMAX: 132,\n  ELIBEXEC: 133,\n  ENOSYS: 52,\n  ENOTEMPTY: 55,\n  ENAMETOOLONG: 37,\n  ELOOP: 32,\n  EOPNOTSUPP: 138,\n  EPFNOSUPPORT: 139,\n  ECONNRESET: 15,\n  ENOBUFS: 42,\n  EAFNOSUPPORT: 5,\n  EPROTOTYPE: 67,\n  ENOTSOCK: 57,\n  ENOPROTOOPT: 50,\n  ESHUTDOWN: 140,\n  ECONNREFUSED: 14,\n  EADDRINUSE: 3,\n  ECONNABORTED: 13,\n  ENETUNREACH: 40,\n  ENETDOWN: 38,\n  ETIMEDOUT: 73,\n  EHOSTDOWN: 142,\n  EHOSTUNREACH: 23,\n  EINPROGRESS: 26,\n  EALREADY: 7,\n  EDESTADDRREQ: 17,\n  EMSGSIZE: 35,\n  EPROTONOSUPPORT: 66,\n  ESOCKTNOSUPPORT: 137,\n  EADDRNOTAVAIL: 4,\n  ENETRESET: 39,\n  EISCONN: 30,\n  ENOTCONN: 53,\n  ETOOMANYREFS: 141,\n  EUSERS: 136,\n  EDQUOT: 19,\n  ESTALE: 72,\n  ENOTSUP: 138,\n  ENOMEDIUM: 148,\n  EILSEQ: 25,\n  EOVERFLOW: 61,\n  ECANCELED: 11,\n  ENOTRECOVERABLE: 56,\n  EOWNERDEAD: 62,\n  ESTRPIPE: 135\n};\nvar FS = {\n  root: null,\n  mounts: [],\n  devices: {},\n  streams: [],\n  nextInode: 1,\n  nameTable: null,\n  currentPath: '/',\n  initialized: false,\n  ignorePermissions: true,\n  ErrnoError: class extends Error {\n    // We set the `name` property to be able to identify `FS.ErrnoError`\n    // - the `name` is a standard ECMA-262 property of error objects. Kind of good to have it anyway.\n    // - when using PROXYFS, an error can come from an underlying FS\n    // as different FS objects have their own FS.ErrnoError each,\n    // the test `err instanceof FS.ErrnoError` won't detect an error coming from another filesystem, causing bugs.\n    // we'll use the reliable test `err.name == \"ErrnoError\"` instead\n    constructor(errno) {\n      super(ERRNO_MESSAGES[errno]);\n      // TODO(sbc): Use the inline member declaration syntax once we\n      // support it in acorn and closure.\n      this.name = 'ErrnoError';\n      this.errno = errno;\n      for (var key in ERRNO_CODES) {\n        if (ERRNO_CODES[key] === errno) {\n          this.code = key;\n          break;\n        }\n      }\n    }\n  },\n  genericErrors: {},\n  filesystems: null,\n  syncFSRequests: 0,\n  FSStream: class {\n    constructor() {\n      // TODO(https://github.com/emscripten-core/emscripten/issues/21414):\n      // Use inline field declarations.\n      this.shared = {};\n    }\n    get object() {\n      return this.node;\n    }\n    set object(val) {\n      this.node = val;\n    }\n    get isRead() {\n      return (this.flags & 2097155) !== 1;\n    }\n    get isWrite() {\n      return (this.flags & 2097155) !== 0;\n    }\n    get isAppend() {\n      return this.flags & 1024;\n    }\n    get flags() {\n      return this.shared.flags;\n    }\n    set flags(val) {\n      this.shared.flags = val;\n    }\n    get position() {\n      return this.shared.position;\n    }\n    set position(val) {\n      this.shared.position = val;\n    }\n  },\n  FSNode: class {\n    constructor(parent, name, mode, rdev) {\n      if (!parent) {\n        parent = this; // root node sets parent to itself\n      }\n      this.parent = parent;\n      this.mount = parent.mount;\n      this.mounted = null;\n      this.id = FS.nextInode++;\n      this.name = name;\n      this.mode = mode;\n      this.node_ops = {};\n      this.stream_ops = {};\n      this.rdev = rdev;\n      this.readMode = 292 /*292*/ | 73 /*73*/;\n      this.writeMode = 146 /*146*/;\n    }\n    get read() {\n      return (this.mode & this.readMode) === this.readMode;\n    }\n    set read(val) {\n      val ? (this.mode |= this.readMode) : (this.mode &= ~this.readMode);\n    }\n    get write() {\n      return (this.mode & this.writeMode) === this.writeMode;\n    }\n    set write(val) {\n      val ? (this.mode |= this.writeMode) : (this.mode &= ~this.writeMode);\n    }\n    get isFolder() {\n      return FS.isDir(this.mode);\n    }\n    get isDevice() {\n      return FS.isChrdev(this.mode);\n    }\n  },\n  lookupPath(path, opts = {}) {\n    path = PATH_FS.resolve(path);\n\n    if (!path) return { path: '', node: null };\n\n    var defaults = {\n      follow_mount: true,\n      recurse_count: 0\n    };\n    opts = Object.assign(defaults, opts);\n\n    if (opts.recurse_count > 8) {\n      // max recursive lookup of 8\n      throw new FS.ErrnoError(32);\n    }\n\n    // split the absolute path\n    var parts = path.split('/').filter((p) => !!p);\n\n    // start at the root\n    var current = FS.root;\n    var current_path = '/';\n\n    for (var i = 0; i < parts.length; i++) {\n      var islast = i === parts.length - 1;\n      if (islast && opts.parent) {\n        // stop resolving\n        break;\n      }\n\n      current = FS.lookupNode(current, parts[i]);\n      current_path = PATH.join2(current_path, parts[i]);\n\n      // jump to the mount's root node if this is a mountpoint\n      if (FS.isMountpoint(current)) {\n        if (!islast || (islast && opts.follow_mount)) {\n          current = current.mounted.root;\n        }\n      }\n\n      // by default, lookupPath will not follow a symlink if it is the final path component.\n      // setting opts.follow = true will override this behavior.\n      if (!islast || opts.follow) {\n        var count = 0;\n        while (FS.isLink(current.mode)) {\n          var link = FS.readlink(current_path);\n          current_path = PATH_FS.resolve(PATH.dirname(current_path), link);\n\n          var lookup = FS.lookupPath(current_path, {\n            recurse_count: opts.recurse_count + 1\n          });\n          current = lookup.node;\n\n          if (count++ > 40) {\n            // limit max consecutive symlinks to 40 (SYMLOOP_MAX).\n            throw new FS.ErrnoError(32);\n          }\n        }\n      }\n    }\n\n    return { path: current_path, node: current };\n  },\n  getPath(node) {\n    var path;\n    while (true) {\n      if (FS.isRoot(node)) {\n        var mount = node.mount.mountpoint;\n        if (!path) return mount;\n        return mount[mount.length - 1] !== '/'\n          ? `${mount}/${path}`\n          : mount + path;\n      }\n      path = path ? `${node.name}/${path}` : node.name;\n      node = node.parent;\n    }\n  },\n  hashName(parentid, name) {\n    var hash = 0;\n\n    for (var i = 0; i < name.length; i++) {\n      hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0;\n    }\n    return ((parentid + hash) >>> 0) % FS.nameTable.length;\n  },\n  hashAddNode(node) {\n    var hash = FS.hashName(node.parent.id, node.name);\n    node.name_next = FS.nameTable[hash];\n    FS.nameTable[hash] = node;\n  },\n  hashRemoveNode(node) {\n    var hash = FS.hashName(node.parent.id, node.name);\n    if (FS.nameTable[hash] === node) {\n      FS.nameTable[hash] = node.name_next;\n    } else {\n      var current = FS.nameTable[hash];\n      while (current) {\n        if (current.name_next === node) {\n          current.name_next = node.name_next;\n          break;\n        }\n        current = current.name_next;\n      }\n    }\n  },\n  lookupNode(parent, name) {\n    var errCode = FS.mayLookup(parent);\n    if (errCode) {\n      throw new FS.ErrnoError(errCode);\n    }\n    var hash = FS.hashName(parent.id, name);\n    for (var node = FS.nameTable[hash]; node; node = node.name_next) {\n      var nodeName = node.name;\n      if (node.parent.id === parent.id && nodeName === name) {\n        return node;\n      }\n    }\n    // if we failed to find it in the cache, call into the VFS\n    return FS.lookup(parent, name);\n  },\n  createNode(parent, name, mode, rdev) {\n    assert(typeof parent == 'object');\n    var node = new FS.FSNode(parent, name, mode, rdev);\n\n    FS.hashAddNode(node);\n\n    return node;\n  },\n  destroyNode(node) {\n    FS.hashRemoveNode(node);\n  },\n  isRoot(node) {\n    return node === node.parent;\n  },\n  isMountpoint(node) {\n    return !!node.mounted;\n  },\n  isFile(mode) {\n    return (mode & 61440) === 32768;\n  },\n  isDir(mode) {\n    return (mode & 61440) === 16384;\n  },\n  isLink(mode) {\n    return (mode & 61440) === 40960;\n  },\n  isChrdev(mode) {\n    return (mode & 61440) === 8192;\n  },\n  isBlkdev(mode) {\n    return (mode & 61440) === 24576;\n  },\n  isFIFO(mode) {\n    return (mode & 61440) === 4096;\n  },\n  isSocket(mode) {\n    return (mode & 49152) === 49152;\n  },\n  flagsToPermissionString(flag) {\n    var perms = ['r', 'w', 'rw'][flag & 3];\n    if (flag & 512) {\n      perms += 'w';\n    }\n    return perms;\n  },\n  nodePermissions(node, perms) {\n    if (FS.ignorePermissions) {\n      return 0;\n    }\n    // return 0 if any user, group or owner bits are set.\n    if (perms.includes('r') && !(node.mode & 292)) {\n      return 2;\n    } else if (perms.includes('w') && !(node.mode & 146)) {\n      return 2;\n    } else if (perms.includes('x') && !(node.mode & 73)) {\n      return 2;\n    }\n    return 0;\n  },\n  mayLookup(dir) {\n    if (!FS.isDir(dir.mode)) return 54;\n    var errCode = FS.nodePermissions(dir, 'x');\n    if (errCode) return errCode;\n    if (!dir.node_ops.lookup) return 2;\n    return 0;\n  },\n  mayCreate(dir, name) {\n    try {\n      var node = FS.lookupNode(dir, name);\n      return 20;\n    } catch (e) {}\n    return FS.nodePermissions(dir, 'wx');\n  },\n  mayDelete(dir, name, isdir) {\n    var node;\n    try {\n      node = FS.lookupNode(dir, name);\n    } catch (e) {\n      return e.errno;\n    }\n    var errCode = FS.nodePermissions(dir, 'wx');\n    if (errCode) {\n      return errCode;\n    }\n    if (isdir) {\n      if (!FS.isDir(node.mode)) {\n        return 54;\n      }\n      if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) {\n        return 10;\n      }\n    } else {\n      if (FS.isDir(node.mode)) {\n        return 31;\n      }\n    }\n    return 0;\n  },\n  mayOpen(node, flags) {\n    if (!node) {\n      return 44;\n    }\n    if (FS.isLink(node.mode)) {\n      return 32;\n    } else if (FS.isDir(node.mode)) {\n      if (\n        FS.flagsToPermissionString(flags) !== 'r' || // opening for write\n        flags & 512\n      ) {\n        // TODO: check for O_SEARCH? (== search for dir only)\n        return 31;\n      }\n    }\n    return FS.nodePermissions(node, FS.flagsToPermissionString(flags));\n  },\n  MAX_OPEN_FDS: 4096,\n  nextfd() {\n    for (var fd = 0; fd <= FS.MAX_OPEN_FDS; fd++) {\n      if (!FS.streams[fd]) {\n        return fd;\n      }\n    }\n    throw new FS.ErrnoError(33);\n  },\n  getStreamChecked(fd) {\n    var stream = FS.getStream(fd);\n    if (!stream) {\n      throw new FS.ErrnoError(8);\n    }\n    return stream;\n  },\n  getStream: (fd) => FS.streams[fd],\n  createStream(stream, fd = -1) {\n    // clone it, so we can return an instance of FSStream\n    stream = Object.assign(new FS.FSStream(), stream);\n    if (fd == -1) {\n      fd = FS.nextfd();\n    }\n    stream.fd = fd;\n    FS.streams[fd] = stream;\n    return stream;\n  },\n  closeStream(fd) {\n    FS.streams[fd] = null;\n  },\n  dupStream(origStream, fd = -1) {\n    var stream = FS.createStream(origStream, fd);\n    stream.stream_ops?.dup?.(stream);\n    return stream;\n  },\n  chrdev_stream_ops: {\n    open(stream) {\n      var device = FS.getDevice(stream.node.rdev);\n      // override node's stream ops with the device's\n      stream.stream_ops = device.stream_ops;\n      // forward the open call\n      stream.stream_ops.open?.(stream);\n    },\n    llseek() {\n      throw new FS.ErrnoError(70);\n    }\n  },\n  major: (dev) => dev >> 8,\n  minor: (dev) => dev & 0xff,\n  makedev: (ma, mi) => (ma << 8) | mi,\n  registerDevice(dev, ops) {\n    FS.devices[dev] = { stream_ops: ops };\n  },\n  getDevice: (dev) => FS.devices[dev],\n  getMounts(mount) {\n    var mounts = [];\n    var check = [mount];\n\n    while (check.length) {\n      var m = check.pop();\n\n      mounts.push(m);\n\n      check.push(...m.mounts);\n    }\n\n    return mounts;\n  },\n  syncfs(populate, callback) {\n    if (typeof populate == 'function') {\n      callback = populate;\n      populate = false;\n    }\n\n    FS.syncFSRequests++;\n\n    if (FS.syncFSRequests > 1) {\n      err(\n        `warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`\n      );\n    }\n\n    var mounts = FS.getMounts(FS.root.mount);\n    var completed = 0;\n\n    function doCallback(errCode) {\n      assert(FS.syncFSRequests > 0);\n      FS.syncFSRequests--;\n      return callback(errCode);\n    }\n\n    function done(errCode) {\n      if (errCode) {\n        if (!done.errored) {\n          done.errored = true;\n          return doCallback(errCode);\n        }\n        return;\n      }\n      if (++completed >= mounts.length) {\n        doCallback(null);\n      }\n    }\n\n    // sync all mounts\n    mounts.forEach((mount) => {\n      if (!mount.type.syncfs) {\n        return done(null);\n      }\n      mount.type.syncfs(mount, populate, done);\n    });\n  },\n  mount(type, opts, mountpoint) {\n    if (typeof type == 'string') {\n      // The filesystem was not included, and instead we have an error\n      // message stored in the variable.\n      throw type;\n    }\n    var root = mountpoint === '/';\n    var pseudo = !mountpoint;\n    var node;\n\n    if (root && FS.root) {\n      throw new FS.ErrnoError(10);\n    } else if (!root && !pseudo) {\n      var lookup = FS.lookupPath(mountpoint, { follow_mount: false });\n\n      mountpoint = lookup.path; // use the absolute path\n      node = lookup.node;\n\n      if (FS.isMountpoint(node)) {\n        throw new FS.ErrnoError(10);\n      }\n\n      if (!FS.isDir(node.mode)) {\n        throw new FS.ErrnoError(54);\n      }\n    }\n\n    var mount = {\n      type,\n      opts,\n      mountpoint,\n      mounts: []\n    };\n\n    // create a root node for the fs\n    var mountRoot = type.mount(mount);\n    mountRoot.mount = mount;\n    mount.root = mountRoot;\n\n    if (root) {\n      FS.root = mountRoot;\n    } else if (node) {\n      // set as a mountpoint\n      node.mounted = mount;\n\n      // add the new mount to the current mount's children\n      if (node.mount) {\n        node.mount.mounts.push(mount);\n      }\n    }\n\n    return mountRoot;\n  },\n  unmount(mountpoint) {\n    var lookup = FS.lookupPath(mountpoint, { follow_mount: false });\n\n    if (!FS.isMountpoint(lookup.node)) {\n      throw new FS.ErrnoError(28);\n    }\n\n    // destroy the nodes for this mount, and all its child mounts\n    var node = lookup.node;\n    var mount = node.mounted;\n    var mounts = FS.getMounts(mount);\n\n    Object.keys(FS.nameTable).forEach((hash) => {\n      var current = FS.nameTable[hash];\n\n      while (current) {\n        var next = current.name_next;\n\n        if (mounts.includes(current.mount)) {\n          FS.destroyNode(current);\n        }\n\n        current = next;\n      }\n    });\n\n    // no longer a mountpoint\n    node.mounted = null;\n\n    // remove this mount from the child mounts\n    var idx = node.mount.mounts.indexOf(mount);\n    assert(idx !== -1);\n    node.mount.mounts.splice(idx, 1);\n  },\n  lookup(parent, name) {\n    return parent.node_ops.lookup(parent, name);\n  },\n  mknod(path, mode, dev) {\n    var lookup = FS.lookupPath(path, { parent: true });\n    var parent = lookup.node;\n    var name = PATH.basename(path);\n    if (!name || name === '.' || name === '..') {\n      throw new FS.ErrnoError(28);\n    }\n    var errCode = FS.mayCreate(parent, name);\n    if (errCode) {\n      throw new FS.ErrnoError(errCode);\n    }\n    if (!parent.node_ops.mknod) {\n      throw new FS.ErrnoError(63);\n    }\n    return parent.node_ops.mknod(parent, name, mode, dev);\n  },\n  create(path, mode) {\n    mode = mode !== undefined ? mode : 438 /* 0666 */;\n    mode &= 4095;\n    mode |= 32768;\n    return FS.mknod(path, mode, 0);\n  },\n  mkdir(path, mode) {\n    mode = mode !== undefined ? mode : 511 /* 0777 */;\n    mode &= 511 | 512;\n    mode |= 16384;\n    return FS.mknod(path, mode, 0);\n  },\n  mkdirTree(path, mode) {\n    var dirs = path.split('/');\n    var d = '';\n    for (var i = 0; i < dirs.length; ++i) {\n      if (!dirs[i]) continue;\n      d += '/' + dirs[i];\n      try {\n        FS.mkdir(d, mode);\n      } catch (e) {\n        if (e.errno != 20) throw e;\n      }\n    }\n  },\n  mkdev(path, mode, dev) {\n    if (typeof dev == 'undefined') {\n      dev = mode;\n      mode = 438 /* 0666 */;\n    }\n    mode |= 8192;\n    return FS.mknod(path, mode, dev);\n  },\n  symlink(oldpath, newpath) {\n    if (!PATH_FS.resolve(oldpath)) {\n      throw new FS.ErrnoError(44);\n    }\n    var lookup = FS.lookupPath(newpath, { parent: true });\n    var parent = lookup.node;\n    if (!parent) {\n      throw new FS.ErrnoError(44);\n    }\n    var newname = PATH.basename(newpath);\n    var errCode = FS.mayCreate(parent, newname);\n    if (errCode) {\n      throw new FS.ErrnoError(errCode);\n    }\n    if (!parent.node_ops.symlink) {\n      throw new FS.ErrnoError(63);\n    }\n    return parent.node_ops.symlink(parent, newname, oldpath);\n  },\n  rename(old_path, new_path) {\n    var old_dirname = PATH.dirname(old_path);\n    var new_dirname = PATH.dirname(new_path);\n    var old_name = PATH.basename(old_path);\n    var new_name = PATH.basename(new_path);\n    // parents must exist\n    var lookup, old_dir, new_dir;\n\n    // let the errors from non existent directories percolate up\n    lookup = FS.lookupPath(old_path, { parent: true });\n    old_dir = lookup.node;\n    lookup = FS.lookupPath(new_path, { parent: true });\n    new_dir = lookup.node;\n\n    if (!old_dir || !new_dir) throw new FS.ErrnoError(44);\n    // need to be part of the same mount\n    if (old_dir.mount !== new_dir.mount) {\n      throw new FS.ErrnoError(75);\n    }\n    // source must exist\n    var old_node = FS.lookupNode(old_dir, old_name);\n    // old path should not be an ancestor of the new path\n    var relative = PATH_FS.relative(old_path, new_dirname);\n    if (relative.charAt(0) !== '.') {\n      throw new FS.ErrnoError(28);\n    }\n    // new path should not be an ancestor of the old path\n    relative = PATH_FS.relative(new_path, old_dirname);\n    if (relative.charAt(0) !== '.') {\n      throw new FS.ErrnoError(55);\n    }\n    // see if the new path already exists\n    var new_node;\n    try {\n      new_node = FS.lookupNode(new_dir, new_name);\n    } catch (e) {\n      // not fatal\n    }\n    // early out if nothing needs to change\n    if (old_node === new_node) {\n      return;\n    }\n    // we'll need to delete the old entry\n    var isdir = FS.isDir(old_node.mode);\n    var errCode = FS.mayDelete(old_dir, old_name, isdir);\n    if (errCode) {\n      throw new FS.ErrnoError(errCode);\n    }\n    // need delete permissions if we'll be overwriting.\n    // need create permissions if new doesn't already exist.\n    errCode = new_node\n      ? FS.mayDelete(new_dir, new_name, isdir)\n      : FS.mayCreate(new_dir, new_name);\n    if (errCode) {\n      throw new FS.ErrnoError(errCode);\n    }\n    if (!old_dir.node_ops.rename) {\n      throw new FS.ErrnoError(63);\n    }\n    if (FS.isMountpoint(old_node) || (new_node && FS.isMountpoint(new_node))) {\n      throw new FS.ErrnoError(10);\n    }\n    // if we are going to change the parent, check write permissions\n    if (new_dir !== old_dir) {\n      errCode = FS.nodePermissions(old_dir, 'w');\n      if (errCode) {\n        throw new FS.ErrnoError(errCode);\n      }\n    }\n    // remove the node from the lookup hash\n    FS.hashRemoveNode(old_node);\n    // do the underlying fs rename\n    try {\n      old_dir.node_ops.rename(old_node, new_dir, new_name);\n    } catch (e) {\n      throw e;\n    } finally {\n      // add the node back to the hash (in case node_ops.rename\n      // changed its name)\n      FS.hashAddNode(old_node);\n    }\n  },\n  rmdir(path) {\n    var lookup = FS.lookupPath(path, { parent: true });\n    var parent = lookup.node;\n    var name = PATH.basename(path);\n    var node = FS.lookupNode(parent, name);\n    var errCode = FS.mayDelete(parent, name, true);\n    if (errCode) {\n      throw new FS.ErrnoError(errCode);\n    }\n    if (!parent.node_ops.rmdir) {\n      throw new FS.ErrnoError(63);\n    }\n    if (FS.isMountpoint(node)) {\n      throw new FS.ErrnoError(10);\n    }\n    parent.node_ops.rmdir(parent, name);\n    FS.destroyNode(node);\n  },\n  readdir(path) {\n    var lookup = FS.lookupPath(path, { follow: true });\n    var node = lookup.node;\n    if (!node.node_ops.readdir) {\n      throw new FS.ErrnoError(54);\n    }\n    return node.node_ops.readdir(node);\n  },\n  unlink(path) {\n    var lookup = FS.lookupPath(path, { parent: true });\n    var parent = lookup.node;\n    if (!parent) {\n      throw new FS.ErrnoError(44);\n    }\n    var name = PATH.basename(path);\n    var node = FS.lookupNode(parent, name);\n    var errCode = FS.mayDelete(parent, name, false);\n    if (errCode) {\n      // According to POSIX, we should map EISDIR to EPERM, but\n      // we instead do what Linux does (and we must, as we use\n      // the musl linux libc).\n      throw new FS.ErrnoError(errCode);\n    }\n    if (!parent.node_ops.unlink) {\n      throw new FS.ErrnoError(63);\n    }\n    if (FS.isMountpoint(node)) {\n      throw new FS.ErrnoError(10);\n    }\n    parent.node_ops.unlink(parent, name);\n    FS.destroyNode(node);\n  },\n  readlink(path) {\n    var lookup = FS.lookupPath(path);\n    var link = lookup.node;\n    if (!link) {\n      throw new FS.ErrnoError(44);\n    }\n    if (!link.node_ops.readlink) {\n      throw new FS.ErrnoError(28);\n    }\n    return PATH_FS.resolve(\n      FS.getPath(link.parent),\n      link.node_ops.readlink(link)\n    );\n  },\n  stat(path, dontFollow) {\n    var lookup = FS.lookupPath(path, { follow: !dontFollow });\n    var node = lookup.node;\n    if (!node) {\n      throw new FS.ErrnoError(44);\n    }\n    if (!node.node_ops.getattr) {\n      throw new FS.ErrnoError(63);\n    }\n    return node.node_ops.getattr(node);\n  },\n  lstat(path) {\n    return FS.stat(path, true);\n  },\n  chmod(path, mode, dontFollow) {\n    var node;\n    if (typeof path == 'string') {\n      var lookup = FS.lookupPath(path, { follow: !dontFollow });\n      node = lookup.node;\n    } else {\n      node = path;\n    }\n    if (!node.node_ops.setattr) {\n      throw new FS.ErrnoError(63);\n    }\n    node.node_ops.setattr(node, {\n      mode: (mode & 4095) | (node.mode & ~4095),\n      timestamp: Date.now()\n    });\n  },\n  lchmod(path, mode) {\n    FS.chmod(path, mode, true);\n  },\n  fchmod(fd, mode) {\n    var stream = FS.getStreamChecked(fd);\n    FS.chmod(stream.node, mode);\n  },\n  chown(path, uid, gid, dontFollow) {\n    var node;\n    if (typeof path == 'string') {\n      var lookup = FS.lookupPath(path, { follow: !dontFollow });\n      node = lookup.node;\n    } else {\n      node = path;\n    }\n    if (!node.node_ops.setattr) {\n      throw new FS.ErrnoError(63);\n    }\n    node.node_ops.setattr(node, {\n      timestamp: Date.now()\n      // we ignore the uid / gid for now\n    });\n  },\n  lchown(path, uid, gid) {\n    FS.chown(path, uid, gid, true);\n  },\n  fchown(fd, uid, gid) {\n    var stream = FS.getStreamChecked(fd);\n    FS.chown(stream.node, uid, gid);\n  },\n  truncate(path, len) {\n    if (len < 0) {\n      throw new FS.ErrnoError(28);\n    }\n    var node;\n    if (typeof path == 'string') {\n      var lookup = FS.lookupPath(path, { follow: true });\n      node = lookup.node;\n    } else {\n      node = path;\n    }\n    if (!node.node_ops.setattr) {\n      throw new FS.ErrnoError(63);\n    }\n    if (FS.isDir(node.mode)) {\n      throw new FS.ErrnoError(31);\n    }\n    if (!FS.isFile(node.mode)) {\n      throw new FS.ErrnoError(28);\n    }\n    var errCode = FS.nodePermissions(node, 'w');\n    if (errCode) {\n      throw new FS.ErrnoError(errCode);\n    }\n    node.node_ops.setattr(node, {\n      size: len,\n      timestamp: Date.now()\n    });\n  },\n  ftruncate(fd, len) {\n    var stream = FS.getStreamChecked(fd);\n    if ((stream.flags & 2097155) === 0) {\n      throw new FS.ErrnoError(28);\n    }\n    FS.truncate(stream.node, len);\n  },\n  utime(path, atime, mtime) {\n    var lookup = FS.lookupPath(path, { follow: true });\n    var node = lookup.node;\n    node.node_ops.setattr(node, {\n      timestamp: Math.max(atime, mtime)\n    });\n  },\n  open(path, flags, mode) {\n    if (path === '') {\n      throw new FS.ErrnoError(44);\n    }\n    flags = typeof flags == 'string' ? FS_modeStringToFlags(flags) : flags;\n    mode = typeof mode == 'undefined' ? 438 /* 0666 */ : mode;\n    if (flags & 64) {\n      mode = (mode & 4095) | 32768;\n    } else {\n      mode = 0;\n    }\n    var node;\n    if (typeof path == 'object') {\n      node = path;\n    } else {\n      path = PATH.normalize(path);\n      try {\n        var lookup = FS.lookupPath(path, {\n          follow: !(flags & 131072)\n        });\n        node = lookup.node;\n      } catch (e) {\n        // ignore\n      }\n    }\n    // perhaps we need to create the node\n    var created = false;\n    if (flags & 64) {\n      if (node) {\n        // if O_CREAT and O_EXCL are set, error out if the node already exists\n        if (flags & 128) {\n          throw new FS.ErrnoError(20);\n        }\n      } else {\n        // node doesn't exist, try to create it\n        node = FS.mknod(path, mode, 0);\n        created = true;\n      }\n    }\n    if (!node) {\n      throw new FS.ErrnoError(44);\n    }\n    // can't truncate a device\n    if (FS.isChrdev(node.mode)) {\n      flags &= ~512;\n    }\n    // if asked only for a directory, then this must be one\n    if (flags & 65536 && !FS.isDir(node.mode)) {\n      throw new FS.ErrnoError(54);\n    }\n    // check permissions, if this is not a file we just created now (it is ok to\n    // create and write to a file with read-only permissions; it is read-only\n    // for later use)\n    if (!created) {\n      var errCode = FS.mayOpen(node, flags);\n      if (errCode) {\n        throw new FS.ErrnoError(errCode);\n      }\n    }\n    // do truncation if necessary\n    if (flags & 512 && !created) {\n      FS.truncate(node, 0);\n    }\n    // we've already handled these, don't pass down to the underlying vfs\n    flags &= ~(128 | 512 | 131072);\n\n    // register the stream with the filesystem\n    var stream = FS.createStream({\n      node,\n      path: FS.getPath(node), // we want the absolute path to the node\n      flags,\n      seekable: true,\n      position: 0,\n      stream_ops: node.stream_ops,\n      // used by the file family libc calls (fopen, fwrite, ferror, etc.)\n      ungotten: [],\n      error: false\n    });\n    // call the new stream's open function\n    if (stream.stream_ops.open) {\n      stream.stream_ops.open(stream);\n    }\n    if (Module['logReadFiles'] && !(flags & 1)) {\n      if (!FS.readFiles) FS.readFiles = {};\n      if (!(path in FS.readFiles)) {\n        FS.readFiles[path] = 1;\n      }\n    }\n    return stream;\n  },\n  close(stream) {\n    if (FS.isClosed(stream)) {\n      throw new FS.ErrnoError(8);\n    }\n    if (stream.getdents) stream.getdents = null; // free readdir state\n    try {\n      if (stream.stream_ops.close) {\n        stream.stream_ops.close(stream);\n      }\n    } catch (e) {\n      throw e;\n    } finally {\n      FS.closeStream(stream.fd);\n    }\n    stream.fd = null;\n  },\n  isClosed(stream) {\n    return stream.fd === null;\n  },\n  llseek(stream, offset, whence) {\n    if (FS.isClosed(stream)) {\n      throw new FS.ErrnoError(8);\n    }\n    if (!stream.seekable || !stream.stream_ops.llseek) {\n      throw new FS.ErrnoError(70);\n    }\n    if (whence != 0 && whence != 1 && whence != 2) {\n      throw new FS.ErrnoError(28);\n    }\n    stream.position = stream.stream_ops.llseek(stream, offset, whence);\n    stream.ungotten = [];\n    return stream.position;\n  },\n  read(stream, buffer, offset, length, position) {\n    assert(offset >= 0);\n    if (length < 0 || position < 0) {\n      throw new FS.ErrnoError(28);\n    }\n    if (FS.isClosed(stream)) {\n      throw new FS.ErrnoError(8);\n    }\n    if ((stream.flags & 2097155) === 1) {\n      throw new FS.ErrnoError(8);\n    }\n    if (FS.isDir(stream.node.mode)) {\n      throw new FS.ErrnoError(31);\n    }\n    if (!stream.stream_ops.read) {\n      throw new FS.ErrnoError(28);\n    }\n    var seeking = typeof position != 'undefined';\n    if (!seeking) {\n      position = stream.position;\n    } else if (!stream.seekable) {\n      throw new FS.ErrnoError(70);\n    }\n    var bytesRead = stream.stream_ops.read(\n      stream,\n      buffer,\n      offset,\n      length,\n      position\n    );\n    if (!seeking) stream.position += bytesRead;\n    return bytesRead;\n  },\n  write(stream, buffer, offset, length, position, canOwn) {\n    assert(offset >= 0);\n    if (length < 0 || position < 0) {\n      throw new FS.ErrnoError(28);\n    }\n    if (FS.isClosed(stream)) {\n      throw new FS.ErrnoError(8);\n    }\n    if ((stream.flags & 2097155) === 0) {\n      throw new FS.ErrnoError(8);\n    }\n    if (FS.isDir(stream.node.mode)) {\n      throw new FS.ErrnoError(31);\n    }\n    if (!stream.stream_ops.write) {\n      throw new FS.ErrnoError(28);\n    }\n    if (stream.seekable && stream.flags & 1024) {\n      // seek to the end before writing in append mode\n      FS.llseek(stream, 0, 2);\n    }\n    var seeking = typeof position != 'undefined';\n    if (!seeking) {\n      position = stream.position;\n    } else if (!stream.seekable) {\n      throw new FS.ErrnoError(70);\n    }\n    var bytesWritten = stream.stream_ops.write(\n      stream,\n      buffer,\n      offset,\n      length,\n      position,\n      canOwn\n    );\n    if (!seeking) stream.position += bytesWritten;\n    return bytesWritten;\n  },\n  allocate(stream, offset, length) {\n    if (FS.isClosed(stream)) {\n      throw new FS.ErrnoError(8);\n    }\n    if (offset < 0 || length <= 0) {\n      throw new FS.ErrnoError(28);\n    }\n    if ((stream.flags & 2097155) === 0) {\n      throw new FS.ErrnoError(8);\n    }\n    if (!FS.isFile(stream.node.mode) && !FS.isDir(stream.node.mode)) {\n      throw new FS.ErrnoError(43);\n    }\n    if (!stream.stream_ops.allocate) {\n      throw new FS.ErrnoError(138);\n    }\n    stream.stream_ops.allocate(stream, offset, length);\n  },\n  mmap(stream, length, position, prot, flags) {\n    // User requests writing to file (prot & PROT_WRITE != 0).\n    // Checking if we have permissions to write to the file unless\n    // MAP_PRIVATE flag is set. According to POSIX spec it is possible\n    // to write to file opened in read-only mode with MAP_PRIVATE flag,\n    // as all modifications will be visible only in the memory of\n    // the current process.\n    if (\n      (prot & 2) !== 0 &&\n      (flags & 2) === 0 &&\n      (stream.flags & 2097155) !== 2\n    ) {\n      throw new FS.ErrnoError(2);\n    }\n    if ((stream.flags & 2097155) === 1) {\n      throw new FS.ErrnoError(2);\n    }\n    if (!stream.stream_ops.mmap) {\n      throw new FS.ErrnoError(43);\n    }\n    return stream.stream_ops.mmap(stream, length, position, prot, flags);\n  },\n  msync(stream, buffer, offset, length, mmapFlags) {\n    assert(offset >= 0);\n    if (!stream.stream_ops.msync) {\n      return 0;\n    }\n    return stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags);\n  },\n  ioctl(stream, cmd, arg) {\n    if (!stream.stream_ops.ioctl) {\n      throw new FS.ErrnoError(59);\n    }\n    return stream.stream_ops.ioctl(stream, cmd, arg);\n  },\n  readFile(path, opts = {}) {\n    opts.flags = opts.flags || 0;\n    opts.encoding = opts.encoding || 'binary';\n    if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') {\n      throw new Error(`Invalid encoding type \"${opts.encoding}\"`);\n    }\n    var ret;\n    var stream = FS.open(path, opts.flags);\n    var stat = FS.stat(path);\n    var length = stat.size;\n    var buf = new Uint8Array(length);\n    FS.read(stream, buf, 0, length, 0);\n    if (opts.encoding === 'utf8') {\n      ret = UTF8ArrayToString(buf, 0);\n    } else if (opts.encoding === 'binary') {\n      ret = buf;\n    }\n    FS.close(stream);\n    return ret;\n  },\n  writeFile(path, data, opts = {}) {\n    opts.flags = opts.flags || 577;\n    var stream = FS.open(path, opts.flags, opts.mode);\n    if (typeof data == 'string') {\n      var buf = new Uint8Array(lengthBytesUTF8(data) + 1);\n      var actualNumBytes = stringToUTF8Array(data, buf, 0, buf.length);\n      FS.write(stream, buf, 0, actualNumBytes, undefined, opts.canOwn);\n    } else if (ArrayBuffer.isView(data)) {\n      FS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn);\n    } else {\n      throw new Error('Unsupported data type');\n    }\n    FS.close(stream);\n  },\n  cwd: () => FS.currentPath,\n  chdir(path) {\n    var lookup = FS.lookupPath(path, { follow: true });\n    if (lookup.node === null) {\n      throw new FS.ErrnoError(44);\n    }\n    if (!FS.isDir(lookup.node.mode)) {\n      throw new FS.ErrnoError(54);\n    }\n    var errCode = FS.nodePermissions(lookup.node, 'x');\n    if (errCode) {\n      throw new FS.ErrnoError(errCode);\n    }\n    FS.currentPath = lookup.path;\n  },\n  createDefaultDirectories() {\n    FS.mkdir('/tmp');\n    FS.mkdir('/home');\n    FS.mkdir('/home/web_user');\n  },\n  createDefaultDevices() {\n    // create /dev\n    FS.mkdir('/dev');\n    // setup /dev/null\n    FS.registerDevice(FS.makedev(1, 3), {\n      read: () => 0,\n      write: (stream, buffer, offset, length, pos) => length\n    });\n    FS.mkdev('/dev/null', FS.makedev(1, 3));\n    // setup /dev/tty and /dev/tty1\n    // stderr needs to print output using err() rather than out()\n    // so we register a second tty just for it.\n    TTY.register(FS.makedev(5, 0), TTY.default_tty_ops);\n    TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops);\n    FS.mkdev('/dev/tty', FS.makedev(5, 0));\n    FS.mkdev('/dev/tty1', FS.makedev(6, 0));\n    // setup /dev/[u]random\n    // use a buffer to avoid overhead of individual crypto calls per byte\n    var randomBuffer = new Uint8Array(1024),\n      randomLeft = 0;\n    var randomByte = () => {\n      if (randomLeft === 0) {\n        randomLeft = randomFill(randomBuffer).byteLength;\n      }\n      return randomBuffer[--randomLeft];\n    };\n    FS.createDevice('/dev', 'random', randomByte);\n    FS.createDevice('/dev', 'urandom', randomByte);\n    // we're not going to emulate the actual shm device,\n    // just create the tmp dirs that reside in it commonly\n    FS.mkdir('/dev/shm');\n    FS.mkdir('/dev/shm/tmp');\n  },\n  createSpecialDirectories() {\n    // create /proc/self/fd which allows /proc/self/fd/6 => readlink gives the\n    // name of the stream for fd 6 (see test_unistd_ttyname)\n    FS.mkdir('/proc');\n    var proc_self = FS.mkdir('/proc/self');\n    FS.mkdir('/proc/self/fd');\n    FS.mount(\n      {\n        mount() {\n          var node = FS.createNode(proc_self, 'fd', 16384 | 511 /* 0777 */, 73);\n          node.node_ops = {\n            lookup(parent, name) {\n              var fd = +name;\n              var stream = FS.getStreamChecked(fd);\n              var ret = {\n                parent: null,\n                mount: { mountpoint: 'fake' },\n                node_ops: { readlink: () => stream.path }\n              };\n              ret.parent = ret; // make it look like a simple root node\n              return ret;\n            }\n          };\n          return node;\n        }\n      },\n      {},\n      '/proc/self/fd'\n    );\n  },\n  createStandardStreams() {\n    // TODO deprecate the old functionality of a single\n    // input / output callback and that utilizes FS.createDevice\n    // and instead require a unique set of stream ops\n\n    // by default, we symlink the standard streams to the\n    // default tty devices. however, if the standard streams\n    // have been overwritten we create a unique device for\n    // them instead.\n    if (Module['stdin']) {\n      FS.createDevice('/dev', 'stdin', Module['stdin']);\n    } else {\n      FS.symlink('/dev/tty', '/dev/stdin');\n    }\n    if (Module['stdout']) {\n      FS.createDevice('/dev', 'stdout', null, Module['stdout']);\n    } else {\n      FS.symlink('/dev/tty', '/dev/stdout');\n    }\n    if (Module['stderr']) {\n      FS.createDevice('/dev', 'stderr', null, Module['stderr']);\n    } else {\n      FS.symlink('/dev/tty1', '/dev/stderr');\n    }\n\n    // open default streams for the stdin, stdout and stderr devices\n    var stdin = FS.open('/dev/stdin', 0);\n    var stdout = FS.open('/dev/stdout', 1);\n    var stderr = FS.open('/dev/stderr', 1);\n    assert(stdin.fd === 0, `invalid handle for stdin (${stdin.fd})`);\n    assert(stdout.fd === 1, `invalid handle for stdout (${stdout.fd})`);\n    assert(stderr.fd === 2, `invalid handle for stderr (${stderr.fd})`);\n  },\n  staticInit() {\n    // Some errors may happen quite a bit, to avoid overhead we reuse them (and suffer a lack of stack info)\n    [44].forEach((code) => {\n      FS.genericErrors[code] = new FS.ErrnoError(code);\n      FS.genericErrors[code].stack = '<generic error, no stack>';\n    });\n\n    FS.nameTable = new Array(4096);\n\n    FS.mount(MEMFS, {}, '/');\n\n    FS.createDefaultDirectories();\n    FS.createDefaultDevices();\n    FS.createSpecialDirectories();\n\n    FS.filesystems = {\n      MEMFS: MEMFS\n    };\n  },\n  init(input, output, error) {\n    assert(\n      !FS.init.initialized,\n      'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'\n    );\n    FS.init.initialized = true;\n\n    // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here\n    Module['stdin'] = input || Module['stdin'];\n    Module['stdout'] = output || Module['stdout'];\n    Module['stderr'] = error || Module['stderr'];\n\n    FS.createStandardStreams();\n  },\n  quit() {\n    FS.init.initialized = false;\n    // force-flush all streams, so we get musl std streams printed out\n    _fflush(0);\n    // close all of our streams\n    for (var i = 0; i < FS.streams.length; i++) {\n      var stream = FS.streams[i];\n      if (!stream) {\n        continue;\n      }\n      FS.close(stream);\n    }\n  },\n  findObject(path, dontResolveLastLink) {\n    var ret = FS.analyzePath(path, dontResolveLastLink);\n    if (!ret.exists) {\n      return null;\n    }\n    return ret.object;\n  },\n  analyzePath(path, dontResolveLastLink) {\n    // operate from within the context of the symlink's target\n    try {\n      var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink });\n      path = lookup.path;\n    } catch (e) {}\n    var ret = {\n      isRoot: false,\n      exists: false,\n      error: 0,\n      name: null,\n      path: null,\n      object: null,\n      parentExists: false,\n      parentPath: null,\n      parentObject: null\n    };\n    try {\n      var lookup = FS.lookupPath(path, { parent: true });\n      ret.parentExists = true;\n      ret.parentPath = lookup.path;\n      ret.parentObject = lookup.node;\n      ret.name = PATH.basename(path);\n      lookup = FS.lookupPath(path, { follow: !dontResolveLastLink });\n      ret.exists = true;\n      ret.path = lookup.path;\n      ret.object = lookup.node;\n      ret.name = lookup.node.name;\n      ret.isRoot = lookup.path === '/';\n    } catch (e) {\n      ret.error = e.errno;\n    }\n    return ret;\n  },\n  createPath(parent, path, canRead, canWrite) {\n    parent = typeof parent == 'string' ? parent : FS.getPath(parent);\n    var parts = path.split('/').reverse();\n    while (parts.length) {\n      var part = parts.pop();\n      if (!part) continue;\n      var current = PATH.join2(parent, part);\n      try {\n        FS.mkdir(current);\n      } catch (e) {\n        // ignore EEXIST\n      }\n      parent = current;\n    }\n    return current;\n  },\n  createFile(parent, name, properties, canRead, canWrite) {\n    var path = PATH.join2(\n      typeof parent == 'string' ? parent : FS.getPath(parent),\n      name\n    );\n    var mode = FS_getMode(canRead, canWrite);\n    return FS.create(path, mode);\n  },\n  createDataFile(parent, name, data, canRead, canWrite, canOwn) {\n    var path = name;\n    if (parent) {\n      parent = typeof parent == 'string' ? parent : FS.getPath(parent);\n      path = name ? PATH.join2(parent, name) : parent;\n    }\n    var mode = FS_getMode(canRead, canWrite);\n    var node = FS.create(path, mode);\n    if (data) {\n      if (typeof data == 'string') {\n        var arr = new Array(data.length);\n        for (var i = 0, len = data.length; i < len; ++i)\n          arr[i] = data.charCodeAt(i);\n        data = arr;\n      }\n      // make sure we can write to the file\n      FS.chmod(node, mode | 146);\n      var stream = FS.open(node, 577);\n      FS.write(stream, data, 0, data.length, 0, canOwn);\n      FS.close(stream);\n      FS.chmod(node, mode);\n    }\n  },\n  createDevice(parent, name, input, output) {\n    var path = PATH.join2(\n      typeof parent == 'string' ? parent : FS.getPath(parent),\n      name\n    );\n    var mode = FS_getMode(!!input, !!output);\n    if (!FS.createDevice.major) FS.createDevice.major = 64;\n    var dev = FS.makedev(FS.createDevice.major++, 0);\n    // Create a fake device that a set of stream ops to emulate\n    // the old behavior.\n    FS.registerDevice(dev, {\n      open(stream) {\n        stream.seekable = false;\n      },\n      close(stream) {\n        // flush any pending line data\n        if (output?.buffer?.length) {\n          output(10);\n        }\n      },\n      read(stream, buffer, offset, length, pos /* ignored */) {\n        var bytesRead = 0;\n        for (var i = 0; i < length; i++) {\n          var result;\n          try {\n            result = input();\n          } catch (e) {\n            throw new FS.ErrnoError(29);\n          }\n          if (result === undefined && bytesRead === 0) {\n            throw new FS.ErrnoError(6);\n          }\n          if (result === null || result === undefined) break;\n          bytesRead++;\n          buffer[offset + i] = result;\n        }\n        if (bytesRead) {\n          stream.node.timestamp = Date.now();\n        }\n        return bytesRead;\n      },\n      write(stream, buffer, offset, length, pos) {\n        for (var i = 0; i < length; i++) {\n          try {\n            output(buffer[offset + i]);\n          } catch (e) {\n            throw new FS.ErrnoError(29);\n          }\n        }\n        if (length) {\n          stream.node.timestamp = Date.now();\n        }\n        return i;\n      }\n    });\n    return FS.mkdev(path, mode, dev);\n  },\n  forceLoadFile(obj) {\n    if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true;\n    if (typeof XMLHttpRequest != 'undefined') {\n      throw new Error(\n        'Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.'\n      );\n    } else if (read_) {\n      // Command-line.\n      try {\n        // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as\n        //          read() will try to parse UTF8.\n        obj.contents = intArrayFromString(read_(obj.url), true);\n        obj.usedBytes = obj.contents.length;\n      } catch (e) {\n        throw new FS.ErrnoError(29);\n      }\n    } else {\n      throw new Error('Cannot load without read() or XMLHttpRequest.');\n    }\n  },\n  createLazyFile(parent, name, url, canRead, canWrite) {\n    // Lazy chunked Uint8Array (implements get and length from Uint8Array).\n    // Actual getting is abstracted away for eventual reuse.\n    class LazyUint8Array {\n      constructor() {\n        this.lengthKnown = false;\n        this.chunks = []; // Loaded chunks. Index is the chunk number\n      }\n      get(idx) {\n        if (idx > this.length - 1 || idx < 0) {\n          return undefined;\n        }\n        var chunkOffset = idx % this.chunkSize;\n        var chunkNum = (idx / this.chunkSize) | 0;\n        return this.getter(chunkNum)[chunkOffset];\n      }\n      setDataGetter(getter) {\n        this.getter = getter;\n      }\n      cacheLength() {\n        // Find length\n        var xhr = new XMLHttpRequest();\n        xhr.open('HEAD', url, false);\n        xhr.send(null);\n        if (!((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304))\n          throw new Error(\"Couldn't load \" + url + '. Status: ' + xhr.status);\n        var datalength = Number(xhr.getResponseHeader('Content-length'));\n        var header;\n        var hasByteServing =\n          (header = xhr.getResponseHeader('Accept-Ranges')) &&\n          header === 'bytes';\n        var usesGzip =\n          (header = xhr.getResponseHeader('Content-Encoding')) &&\n          header === 'gzip';\n\n        var chunkSize = 1024 * 1024; // Chunk size in bytes\n\n        if (!hasByteServing) chunkSize = datalength;\n\n        // Function to get a range from the remote URL.\n        var doXHR = (from, to) => {\n          if (from > to)\n            throw new Error(\n              'invalid range (' + from + ', ' + to + ') or no bytes requested!'\n            );\n          if (to > datalength - 1)\n            throw new Error(\n              'only ' + datalength + ' bytes available! programmer error!'\n            );\n\n          // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available.\n          var xhr = new XMLHttpRequest();\n          xhr.open('GET', url, false);\n          if (datalength !== chunkSize)\n            xhr.setRequestHeader('Range', 'bytes=' + from + '-' + to);\n\n          // Some hints to the browser that we want binary data.\n          xhr.responseType = 'arraybuffer';\n          if (xhr.overrideMimeType) {\n            xhr.overrideMimeType('text/plain; charset=x-user-defined');\n          }\n\n          xhr.send(null);\n          if (!((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304))\n            throw new Error(\"Couldn't load \" + url + '. Status: ' + xhr.status);\n          if (xhr.response !== undefined) {\n            return new Uint8Array(\n              /** @type{Array<number>} */ (xhr.response || [])\n            );\n          }\n          return intArrayFromString(xhr.responseText || '', true);\n        };\n        var lazyArray = this;\n        lazyArray.setDataGetter((chunkNum) => {\n          var start = chunkNum * chunkSize;\n          var end = (chunkNum + 1) * chunkSize - 1; // including this byte\n          end = Math.min(end, datalength - 1); // if datalength-1 is selected, this is the last block\n          if (typeof lazyArray.chunks[chunkNum] == 'undefined') {\n            lazyArray.chunks[chunkNum] = doXHR(start, end);\n          }\n          if (typeof lazyArray.chunks[chunkNum] == 'undefined')\n            throw new Error('doXHR failed!');\n          return lazyArray.chunks[chunkNum];\n        });\n\n        if (usesGzip || !datalength) {\n          // if the server uses gzip or doesn't supply the length, we have to download the whole file to get the (uncompressed) length\n          chunkSize = datalength = 1; // this will force getter(0)/doXHR do download the whole file\n          datalength = this.getter(0).length;\n          chunkSize = datalength;\n          out(\n            'LazyFiles on gzip forces download of the whole file when length is accessed'\n          );\n        }\n\n        this._length = datalength;\n        this._chunkSize = chunkSize;\n        this.lengthKnown = true;\n      }\n      get length() {\n        if (!this.lengthKnown) {\n          this.cacheLength();\n        }\n        return this._length;\n      }\n      get chunkSize() {\n        if (!this.lengthKnown) {\n          this.cacheLength();\n        }\n        return this._chunkSize;\n      }\n    }\n\n    if (typeof XMLHttpRequest != 'undefined') {\n      if (!ENVIRONMENT_IS_WORKER)\n        throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc';\n      var lazyArray = new LazyUint8Array();\n      var properties = { isDevice: false, contents: lazyArray };\n    } else {\n      var properties = { isDevice: false, url: url };\n    }\n\n    var node = FS.createFile(parent, name, properties, canRead, canWrite);\n    // This is a total hack, but I want to get this lazy file code out of the\n    // core of MEMFS. If we want to keep this lazy file concept I feel it should\n    // be its own thin LAZYFS proxying calls to MEMFS.\n    if (properties.contents) {\n      node.contents = properties.contents;\n    } else if (properties.url) {\n      node.contents = null;\n      node.url = properties.url;\n    }\n    // Add a function that defers querying the file size until it is asked the first time.\n    Object.defineProperties(node, {\n      usedBytes: {\n        get: function () {\n          return this.contents.length;\n        }\n      }\n    });\n    // override each stream op with one that tries to force load the lazy file first\n    var stream_ops = {};\n    var keys = Object.keys(node.stream_ops);\n    keys.forEach((key) => {\n      var fn = node.stream_ops[key];\n      stream_ops[key] = (...args) => {\n        FS.forceLoadFile(node);\n        return fn(...args);\n      };\n    });\n    function writeChunks(stream, buffer, offset, length, position) {\n      var contents = stream.node.contents;\n      if (position >= contents.length) return 0;\n      var size = Math.min(contents.length - position, length);\n      assert(size >= 0);\n      if (contents.slice) {\n        // normal array\n        for (var i = 0; i < size; i++) {\n          buffer[offset + i] = contents[position + i];\n        }\n      } else {\n        for (var i = 0; i < size; i++) {\n          // LazyUint8Array from sync binary XHR\n          buffer[offset + i] = contents.get(position + i);\n        }\n      }\n      return size;\n    }\n    // use a custom read function\n    stream_ops.read = (stream, buffer, offset, length, position) => {\n      FS.forceLoadFile(node);\n      return writeChunks(stream, buffer, offset, length, position);\n    };\n    // use a custom mmap function\n    stream_ops.mmap = (stream, length, position, prot, flags) => {\n      FS.forceLoadFile(node);\n      var ptr = mmapAlloc(length);\n      if (!ptr) {\n        throw new FS.ErrnoError(48);\n      }\n      writeChunks(stream, HEAP8, ptr, length, position);\n      return { ptr, allocated: true };\n    };\n    node.stream_ops = stream_ops;\n    return node;\n  },\n  absolutePath() {\n    abort('FS.absolutePath has been removed; use PATH_FS.resolve instead');\n  },\n  createFolder() {\n    abort('FS.createFolder has been removed; use FS.mkdir instead');\n  },\n  createLink() {\n    abort('FS.createLink has been removed; use FS.symlink instead');\n  },\n  joinPath() {\n    abort('FS.joinPath has been removed; use PATH.join instead');\n  },\n  mmapAlloc() {\n    abort('FS.mmapAlloc has been replaced by the top level function mmapAlloc');\n  },\n  standardizePath() {\n    abort('FS.standardizePath has been removed; use PATH.normalize instead');\n  }\n};\n\nvar SYSCALLS = {\n  DEFAULT_POLLMASK: 5,\n  calculateAt(dirfd, path, allowEmpty) {\n    if (PATH.isAbs(path)) {\n      return path;\n    }\n    // relative path\n    var dir;\n    if (dirfd === -100) {\n      dir = FS.cwd();\n    } else {\n      var dirstream = SYSCALLS.getStreamFromFD(dirfd);\n      dir = dirstream.path;\n    }\n    if (path.length == 0) {\n      if (!allowEmpty) {\n        throw new FS.ErrnoError(44);\n      }\n      return dir;\n    }\n    return PATH.join2(dir, path);\n  },\n  doStat(func, path, buf) {\n    var stat = func(path);\n    HEAP32[buf >> 2] = stat.dev;\n    HEAP32[(buf + 4) >> 2] = stat.mode;\n    HEAPU32[(buf + 8) >> 2] = stat.nlink;\n    HEAP32[(buf + 12) >> 2] = stat.uid;\n    HEAP32[(buf + 16) >> 2] = stat.gid;\n    HEAP32[(buf + 20) >> 2] = stat.rdev;\n    (tempI64 = [\n      stat.size >>> 0,\n      ((tempDouble = stat.size),\n      +Math.abs(tempDouble) >= 1.0\n        ? tempDouble > 0.0\n          ? +Math.floor(tempDouble / 4294967296.0) >>> 0\n          : ~~+Math.ceil(\n              (tempDouble - +(~~tempDouble >>> 0)) / 4294967296.0\n            ) >>> 0\n        : 0)\n    ]),\n      (HEAP32[(buf + 24) >> 2] = tempI64[0]),\n      (HEAP32[(buf + 28) >> 2] = tempI64[1]);\n    HEAP32[(buf + 32) >> 2] = 4096;\n    HEAP32[(buf + 36) >> 2] = stat.blocks;\n    var atime = stat.atime.getTime();\n    var mtime = stat.mtime.getTime();\n    var ctime = stat.ctime.getTime();\n    (tempI64 = [\n      Math.floor(atime / 1000) >>> 0,\n      ((tempDouble = Math.floor(atime / 1000)),\n      +Math.abs(tempDouble) >= 1.0\n        ? tempDouble > 0.0\n          ? +Math.floor(tempDouble / 4294967296.0) >>> 0\n          : ~~+Math.ceil(\n              (tempDouble - +(~~tempDouble >>> 0)) / 4294967296.0\n            ) >>> 0\n        : 0)\n    ]),\n      (HEAP32[(buf + 40) >> 2] = tempI64[0]),\n      (HEAP32[(buf + 44) >> 2] = tempI64[1]);\n    HEAPU32[(buf + 48) >> 2] = (atime % 1000) * 1000;\n    (tempI64 = [\n      Math.floor(mtime / 1000) >>> 0,\n      ((tempDouble = Math.floor(mtime / 1000)),\n      +Math.abs(tempDouble) >= 1.0\n        ? tempDouble > 0.0\n          ? +Math.floor(tempDouble / 4294967296.0) >>> 0\n          : ~~+Math.ceil(\n              (tempDouble - +(~~tempDouble >>> 0)) / 4294967296.0\n            ) >>> 0\n        : 0)\n    ]),\n      (HEAP32[(buf + 56) >> 2] = tempI64[0]),\n      (HEAP32[(buf + 60) >> 2] = tempI64[1]);\n    HEAPU32[(buf + 64) >> 2] = (mtime % 1000) * 1000;\n    (tempI64 = [\n      Math.floor(ctime / 1000) >>> 0,\n      ((tempDouble = Math.floor(ctime / 1000)),\n      +Math.abs(tempDouble) >= 1.0\n        ? tempDouble > 0.0\n          ? +Math.floor(tempDouble / 4294967296.0) >>> 0\n          : ~~+Math.ceil(\n              (tempDouble - +(~~tempDouble >>> 0)) / 4294967296.0\n            ) >>> 0\n        : 0)\n    ]),\n      (HEAP32[(buf + 72) >> 2] = tempI64[0]),\n      (HEAP32[(buf + 76) >> 2] = tempI64[1]);\n    HEAPU32[(buf + 80) >> 2] = (ctime % 1000) * 1000;\n    (tempI64 = [\n      stat.ino >>> 0,\n      ((tempDouble = stat.ino),\n      +Math.abs(tempDouble) >= 1.0\n        ? tempDouble > 0.0\n          ? +Math.floor(tempDouble / 4294967296.0) >>> 0\n          : ~~+Math.ceil(\n              (tempDouble - +(~~tempDouble >>> 0)) / 4294967296.0\n            ) >>> 0\n        : 0)\n    ]),\n      (HEAP32[(buf + 88) >> 2] = tempI64[0]),\n      (HEAP32[(buf + 92) >> 2] = tempI64[1]);\n    return 0;\n  },\n  doMsync(addr, stream, len, flags, offset) {\n    if (!FS.isFile(stream.node.mode)) {\n      throw new FS.ErrnoError(43);\n    }\n    if (flags & 2) {\n      // MAP_PRIVATE calls need not to be synced back to underlying fs\n      return 0;\n    }\n    var buffer = HEAPU8.slice(addr, addr + len);\n    FS.msync(stream, buffer, offset, len, flags);\n  },\n  varargs: undefined,\n  get() {\n    assert(SYSCALLS.varargs != undefined);\n    // the `+` prepended here is necessary to convince the JSCompiler that varargs is indeed a number.\n    var ret = HEAP32[+SYSCALLS.varargs >> 2];\n    SYSCALLS.varargs += 4;\n    return ret;\n  },\n  getp() {\n    return SYSCALLS.get();\n  },\n  getStr(ptr) {\n    var ret = UTF8ToString(ptr);\n    return ret;\n  },\n  getStreamFromFD(fd) {\n    var stream = FS.getStreamChecked(fd);\n    return stream;\n  }\n};\nfunction ___syscall_dup(fd) {\n  try {\n    var old = SYSCALLS.getStreamFromFD(fd);\n    return FS.dupStream(old).fd;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nfunction ___syscall_dup3(fd, newfd, flags) {\n  try {\n    var old = SYSCALLS.getStreamFromFD(fd);\n    assert(!flags);\n    if (old.fd === newfd) return -28;\n    var existing = FS.getStream(newfd);\n    if (existing) FS.close(existing);\n    return FS.dupStream(old, newfd).fd;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nfunction ___syscall_fcntl64(fd, cmd, varargs) {\n  SYSCALLS.varargs = varargs;\n  try {\n    var stream = SYSCALLS.getStreamFromFD(fd);\n    switch (cmd) {\n      case 0: {\n        var arg = SYSCALLS.get();\n        if (arg < 0) {\n          return -28;\n        }\n        while (FS.streams[arg]) {\n          arg++;\n        }\n        var newStream;\n        newStream = FS.dupStream(stream, arg);\n        return newStream.fd;\n      }\n      case 1:\n      case 2:\n        return 0; // FD_CLOEXEC makes no sense for a single process.\n      case 3:\n        return stream.flags;\n      case 4: {\n        var arg = SYSCALLS.get();\n        stream.flags |= arg;\n        return 0;\n      }\n      case 12: {\n        var arg = SYSCALLS.getp();\n        var offset = 0;\n        // We're always unlocked.\n        HEAP16[(arg + offset) >> 1] = 2;\n        return 0;\n      }\n      case 13:\n      case 14:\n        return 0; // Pretend that the locking is successful.\n    }\n    return -28;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nfunction ___syscall_fstat64(fd, buf) {\n  try {\n    var stream = SYSCALLS.getStreamFromFD(fd);\n    return SYSCALLS.doStat(FS.stat, stream.path, buf);\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nvar stringToUTF8 = (str, outPtr, maxBytesToWrite) => {\n  assert(\n    typeof maxBytesToWrite == 'number',\n    'stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!'\n  );\n  return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);\n};\n\nfunction ___syscall_getdents64(fd, dirp, count) {\n  try {\n    var stream = SYSCALLS.getStreamFromFD(fd);\n    stream.getdents ||= FS.readdir(stream.path);\n\n    var struct_size = 280;\n    var pos = 0;\n    var off = FS.llseek(stream, 0, 1);\n\n    var idx = Math.floor(off / struct_size);\n\n    while (idx < stream.getdents.length && pos + struct_size <= count) {\n      var id;\n      var type;\n      var name = stream.getdents[idx];\n      if (name === '.') {\n        id = stream.node.id;\n        type = 4; // DT_DIR\n      } else if (name === '..') {\n        var lookup = FS.lookupPath(stream.path, { parent: true });\n        id = lookup.node.id;\n        type = 4; // DT_DIR\n      } else {\n        var child = FS.lookupNode(stream.node, name);\n        id = child.id;\n        type = FS.isChrdev(child.mode)\n          ? 2 // DT_CHR, character device.\n          : FS.isDir(child.mode)\n            ? 4 // DT_DIR, directory.\n            : FS.isLink(child.mode)\n              ? 10 // DT_LNK, symbolic link.\n              : 8; // DT_REG, regular file.\n      }\n      assert(id);\n      (tempI64 = [\n        id >>> 0,\n        ((tempDouble = id),\n        +Math.abs(tempDouble) >= 1.0\n          ? tempDouble > 0.0\n            ? +Math.floor(tempDouble / 4294967296.0) >>> 0\n            : ~~+Math.ceil(\n                (tempDouble - +(~~tempDouble >>> 0)) / 4294967296.0\n              ) >>> 0\n          : 0)\n      ]),\n        (HEAP32[(dirp + pos) >> 2] = tempI64[0]),\n        (HEAP32[(dirp + pos + 4) >> 2] = tempI64[1]);\n      (tempI64 = [\n        ((idx + 1) * struct_size) >>> 0,\n        ((tempDouble = (idx + 1) * struct_size),\n        +Math.abs(tempDouble) >= 1.0\n          ? tempDouble > 0.0\n            ? +Math.floor(tempDouble / 4294967296.0) >>> 0\n            : ~~+Math.ceil(\n                (tempDouble - +(~~tempDouble >>> 0)) / 4294967296.0\n              ) >>> 0\n          : 0)\n      ]),\n        (HEAP32[(dirp + pos + 8) >> 2] = tempI64[0]),\n        (HEAP32[(dirp + pos + 12) >> 2] = tempI64[1]);\n      HEAP16[(dirp + pos + 16) >> 1] = 280;\n      HEAP8[dirp + pos + 18] = type;\n      stringToUTF8(name, dirp + pos + 19, 256);\n      pos += struct_size;\n      idx += 1;\n    }\n    FS.llseek(stream, idx * struct_size, 0);\n    return pos;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nfunction ___syscall_ioctl(fd, op, varargs) {\n  SYSCALLS.varargs = varargs;\n  try {\n    var stream = SYSCALLS.getStreamFromFD(fd);\n    switch (op) {\n      case 21509: {\n        if (!stream.tty) return -59;\n        return 0;\n      }\n      case 21505: {\n        if (!stream.tty) return -59;\n        if (stream.tty.ops.ioctl_tcgets) {\n          var termios = stream.tty.ops.ioctl_tcgets(stream);\n          var argp = SYSCALLS.getp();\n          HEAP32[argp >> 2] = termios.c_iflag || 0;\n          HEAP32[(argp + 4) >> 2] = termios.c_oflag || 0;\n          HEAP32[(argp + 8) >> 2] = termios.c_cflag || 0;\n          HEAP32[(argp + 12) >> 2] = termios.c_lflag || 0;\n          for (var i = 0; i < 32; i++) {\n            HEAP8[argp + i + 17] = termios.c_cc[i] || 0;\n          }\n          return 0;\n        }\n        return 0;\n      }\n      case 21510:\n      case 21511:\n      case 21512: {\n        if (!stream.tty) return -59;\n        return 0; // no-op, not actually adjusting terminal settings\n      }\n      case 21506:\n      case 21507:\n      case 21508: {\n        if (!stream.tty) return -59;\n        if (stream.tty.ops.ioctl_tcsets) {\n          var argp = SYSCALLS.getp();\n          var c_iflag = HEAP32[argp >> 2];\n          var c_oflag = HEAP32[(argp + 4) >> 2];\n          var c_cflag = HEAP32[(argp + 8) >> 2];\n          var c_lflag = HEAP32[(argp + 12) >> 2];\n          var c_cc = [];\n          for (var i = 0; i < 32; i++) {\n            c_cc.push(HEAP8[argp + i + 17]);\n          }\n          return stream.tty.ops.ioctl_tcsets(stream.tty, op, {\n            c_iflag,\n            c_oflag,\n            c_cflag,\n            c_lflag,\n            c_cc\n          });\n        }\n        return 0; // no-op, not actually adjusting terminal settings\n      }\n      case 21519: {\n        if (!stream.tty) return -59;\n        var argp = SYSCALLS.getp();\n        HEAP32[argp >> 2] = 0;\n        return 0;\n      }\n      case 21520: {\n        if (!stream.tty) return -59;\n        return -28; // not supported\n      }\n      case 21531: {\n        var argp = SYSCALLS.getp();\n        return FS.ioctl(stream, op, argp);\n      }\n      case 21523: {\n        // TODO: in theory we should write to the winsize struct that gets\n        // passed in, but for now musl doesn't read anything on it\n        if (!stream.tty) return -59;\n        if (stream.tty.ops.ioctl_tiocgwinsz) {\n          var winsize = stream.tty.ops.ioctl_tiocgwinsz(stream.tty);\n          var argp = SYSCALLS.getp();\n          HEAP16[argp >> 1] = winsize[0];\n          HEAP16[(argp + 2) >> 1] = winsize[1];\n        }\n        return 0;\n      }\n      case 21524: {\n        // TODO: technically, this ioctl call should change the window size.\n        // but, since emscripten doesn't have any concept of a terminal window\n        // yet, we'll just silently throw it away as we do TIOCGWINSZ\n        if (!stream.tty) return -59;\n        return 0;\n      }\n      case 21515: {\n        if (!stream.tty) return -59;\n        return 0;\n      }\n      default:\n        return -28; // not supported\n    }\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nfunction ___syscall_lstat64(path, buf) {\n  try {\n    path = SYSCALLS.getStr(path);\n    return SYSCALLS.doStat(FS.lstat, path, buf);\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nfunction ___syscall_newfstatat(dirfd, path, buf, flags) {\n  try {\n    path = SYSCALLS.getStr(path);\n    var nofollow = flags & 256;\n    var allowEmpty = flags & 4096;\n    flags = flags & ~6400;\n    assert(!flags, `unknown flags in __syscall_newfstatat: ${flags}`);\n    path = SYSCALLS.calculateAt(dirfd, path, allowEmpty);\n    return SYSCALLS.doStat(nofollow ? FS.lstat : FS.stat, path, buf);\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nfunction ___syscall_openat(dirfd, path, flags, varargs) {\n  SYSCALLS.varargs = varargs;\n  try {\n    path = SYSCALLS.getStr(path);\n    path = SYSCALLS.calculateAt(dirfd, path);\n    var mode = varargs ? SYSCALLS.get() : 0;\n    return FS.open(path, flags, mode).fd;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nfunction ___syscall_renameat(olddirfd, oldpath, newdirfd, newpath) {\n  try {\n    oldpath = SYSCALLS.getStr(oldpath);\n    newpath = SYSCALLS.getStr(newpath);\n    oldpath = SYSCALLS.calculateAt(olddirfd, oldpath);\n    newpath = SYSCALLS.calculateAt(newdirfd, newpath);\n    FS.rename(oldpath, newpath);\n    return 0;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nfunction ___syscall_rmdir(path) {\n  try {\n    path = SYSCALLS.getStr(path);\n    FS.rmdir(path);\n    return 0;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nfunction ___syscall_stat64(path, buf) {\n  try {\n    path = SYSCALLS.getStr(path);\n    return SYSCALLS.doStat(FS.stat, path, buf);\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nfunction ___syscall_unlinkat(dirfd, path, flags) {\n  try {\n    path = SYSCALLS.getStr(path);\n    path = SYSCALLS.calculateAt(dirfd, path);\n    if (flags === 0) {\n      FS.unlink(path);\n    } else if (flags === 512) {\n      FS.rmdir(path);\n    } else {\n      abort('Invalid flags passed to unlinkat');\n    }\n    return 0;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return -e.errno;\n  }\n}\n\nvar nowIsMonotonic = 1;\nvar __emscripten_get_now_is_monotonic = () => nowIsMonotonic;\n\nvar __emscripten_throw_longjmp = () => {\n  throw Infinity;\n};\n\nvar convertI32PairToI53Checked = (lo, hi) => {\n  assert(lo == lo >>> 0 || lo == (lo | 0)); // lo should either be a i32 or a u32\n  assert(hi === (hi | 0)); // hi should be a i32\n  return (hi + 0x200000) >>> 0 < 0x400001 - !!lo\n    ? (lo >>> 0) + hi * 4294967296\n    : NaN;\n};\nfunction __gmtime_js(time_low, time_high, tmPtr) {\n  var time = convertI32PairToI53Checked(time_low, time_high);\n\n  var date = new Date(time * 1000);\n  HEAP32[tmPtr >> 2] = date.getUTCSeconds();\n  HEAP32[(tmPtr + 4) >> 2] = date.getUTCMinutes();\n  HEAP32[(tmPtr + 8) >> 2] = date.getUTCHours();\n  HEAP32[(tmPtr + 12) >> 2] = date.getUTCDate();\n  HEAP32[(tmPtr + 16) >> 2] = date.getUTCMonth();\n  HEAP32[(tmPtr + 20) >> 2] = date.getUTCFullYear() - 1900;\n  HEAP32[(tmPtr + 24) >> 2] = date.getUTCDay();\n  var start = Date.UTC(date.getUTCFullYear(), 0, 1, 0, 0, 0, 0);\n  var yday = ((date.getTime() - start) / (1000 * 60 * 60 * 24)) | 0;\n  HEAP32[(tmPtr + 28) >> 2] = yday;\n}\n\nvar isLeapYear = (year) =>\n  year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);\n\nvar MONTH_DAYS_LEAP_CUMULATIVE = [\n  0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335\n];\n\nvar MONTH_DAYS_REGULAR_CUMULATIVE = [\n  0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334\n];\nvar ydayFromDate = (date) => {\n  var leap = isLeapYear(date.getFullYear());\n  var monthDaysCumulative = leap\n    ? MONTH_DAYS_LEAP_CUMULATIVE\n    : MONTH_DAYS_REGULAR_CUMULATIVE;\n  var yday = monthDaysCumulative[date.getMonth()] + date.getDate() - 1; // -1 since it's days since Jan 1\n\n  return yday;\n};\n\nfunction __localtime_js(time_low, time_high, tmPtr) {\n  var time = convertI32PairToI53Checked(time_low, time_high);\n\n  var date = new Date(time * 1000);\n  HEAP32[tmPtr >> 2] = date.getSeconds();\n  HEAP32[(tmPtr + 4) >> 2] = date.getMinutes();\n  HEAP32[(tmPtr + 8) >> 2] = date.getHours();\n  HEAP32[(tmPtr + 12) >> 2] = date.getDate();\n  HEAP32[(tmPtr + 16) >> 2] = date.getMonth();\n  HEAP32[(tmPtr + 20) >> 2] = date.getFullYear() - 1900;\n  HEAP32[(tmPtr + 24) >> 2] = date.getDay();\n\n  var yday = ydayFromDate(date) | 0;\n  HEAP32[(tmPtr + 28) >> 2] = yday;\n  HEAP32[(tmPtr + 36) >> 2] = -(date.getTimezoneOffset() * 60);\n\n  // Attention: DST is in December in South, and some regions don't have DST at all.\n  var start = new Date(date.getFullYear(), 0, 1);\n  var summerOffset = new Date(date.getFullYear(), 6, 1).getTimezoneOffset();\n  var winterOffset = start.getTimezoneOffset();\n  var dst =\n    (summerOffset != winterOffset &&\n      date.getTimezoneOffset() == Math.min(winterOffset, summerOffset)) | 0;\n  HEAP32[(tmPtr + 32) >> 2] = dst;\n}\n\nvar __mktime_js = function (tmPtr) {\n  var ret = (() => {\n    var date = new Date(\n      HEAP32[(tmPtr + 20) >> 2] + 1900,\n      HEAP32[(tmPtr + 16) >> 2],\n      HEAP32[(tmPtr + 12) >> 2],\n      HEAP32[(tmPtr + 8) >> 2],\n      HEAP32[(tmPtr + 4) >> 2],\n      HEAP32[tmPtr >> 2],\n      0\n    );\n\n    // There's an ambiguous hour when the time goes back; the tm_isdst field is\n    // used to disambiguate it.  Date() basically guesses, so we fix it up if it\n    // guessed wrong, or fill in tm_isdst with the guess if it's -1.\n    var dst = HEAP32[(tmPtr + 32) >> 2];\n    var guessedOffset = date.getTimezoneOffset();\n    var start = new Date(date.getFullYear(), 0, 1);\n    var summerOffset = new Date(date.getFullYear(), 6, 1).getTimezoneOffset();\n    var winterOffset = start.getTimezoneOffset();\n    var dstOffset = Math.min(winterOffset, summerOffset); // DST is in December in South\n    if (dst < 0) {\n      // Attention: some regions don't have DST at all.\n      HEAP32[(tmPtr + 32) >> 2] = Number(\n        summerOffset != winterOffset && dstOffset == guessedOffset\n      );\n    } else if (dst > 0 != (dstOffset == guessedOffset)) {\n      var nonDstOffset = Math.max(winterOffset, summerOffset);\n      var trueOffset = dst > 0 ? dstOffset : nonDstOffset;\n      // Don't try setMinutes(date.getMinutes() + ...) -- it's messed up.\n      date.setTime(date.getTime() + (trueOffset - guessedOffset) * 60000);\n    }\n\n    HEAP32[(tmPtr + 24) >> 2] = date.getDay();\n    var yday = ydayFromDate(date) | 0;\n    HEAP32[(tmPtr + 28) >> 2] = yday;\n    // To match expected behavior, update fields from date\n    HEAP32[tmPtr >> 2] = date.getSeconds();\n    HEAP32[(tmPtr + 4) >> 2] = date.getMinutes();\n    HEAP32[(tmPtr + 8) >> 2] = date.getHours();\n    HEAP32[(tmPtr + 12) >> 2] = date.getDate();\n    HEAP32[(tmPtr + 16) >> 2] = date.getMonth();\n    HEAP32[(tmPtr + 20) >> 2] = date.getYear();\n\n    var timeMs = date.getTime();\n    if (isNaN(timeMs)) {\n      return -1;\n    }\n    // Return time in microseconds\n    return timeMs / 1000;\n  })();\n  return (\n    setTempRet0(\n      ((tempDouble = ret),\n      +Math.abs(tempDouble) >= 1.0\n        ? tempDouble > 0.0\n          ? +Math.floor(tempDouble / 4294967296.0) >>> 0\n          : ~~+Math.ceil(\n              (tempDouble - +(~~tempDouble >>> 0)) / 4294967296.0\n            ) >>> 0\n        : 0)\n    ),\n    ret >>> 0\n  );\n};\n\nvar __tzset_js = (timezone, daylight, std_name, dst_name) => {\n  // TODO: Use (malleable) environment variables instead of system settings.\n  var currentYear = new Date().getFullYear();\n  var winter = new Date(currentYear, 0, 1);\n  var summer = new Date(currentYear, 6, 1);\n  var winterOffset = winter.getTimezoneOffset();\n  var summerOffset = summer.getTimezoneOffset();\n\n  // Local standard timezone offset. Local standard time is not adjusted for\n  // daylight savings.  This code uses the fact that getTimezoneOffset returns\n  // a greater value during Standard Time versus Daylight Saving Time (DST).\n  // Thus it determines the expected output during Standard Time, and it\n  // compares whether the output of the given date the same (Standard) or less\n  // (DST).\n  var stdTimezoneOffset = Math.max(winterOffset, summerOffset);\n\n  // timezone is specified as seconds west of UTC (\"The external variable\n  // `timezone` shall be set to the difference, in seconds, between\n  // Coordinated Universal Time (UTC) and local standard time.\"), the same\n  // as returned by stdTimezoneOffset.\n  // See http://pubs.opengroup.org/onlinepubs/009695399/functions/tzset.html\n  HEAPU32[timezone >> 2] = stdTimezoneOffset * 60;\n\n  HEAP32[daylight >> 2] = Number(winterOffset != summerOffset);\n\n  function extractZone(date) {\n    var match = date.toTimeString().match(/\\(([A-Za-z ]+)\\)$/);\n    return match ? match[1] : 'GMT';\n  }\n  var winterName = extractZone(winter);\n  var summerName = extractZone(summer);\n  if (summerOffset < winterOffset) {\n    // Northern hemisphere\n    stringToUTF8(winterName, std_name, 7);\n    stringToUTF8(summerName, dst_name, 7);\n  } else {\n    stringToUTF8(winterName, dst_name, 7);\n    stringToUTF8(summerName, std_name, 7);\n  }\n};\n\nvar _emscripten_date_now = () => Date.now();\n\nvar _emscripten_get_now;\n// Modern environment where performance.now() is supported:\n// N.B. a shorter form \"_emscripten_get_now = performance.now;\" is\n// unfortunately not allowed even in current browsers (e.g. FF Nightly 75).\n_emscripten_get_now = () => performance.now();\nvar _emscripten_memcpy_js = (dest, src, num) =>\n  HEAPU8.copyWithin(dest, src, src + num);\n\nvar getHeapMax = () =>\n  // Stay one Wasm page short of 4GB: while e.g. Chrome is able to allocate\n  // full 4GB Wasm memories, the size will wrap back to 0 bytes in Wasm side\n  // for any code that deals with heap sizes, which would require special\n  // casing all heap size related code to treat 0 specially.\n  2147483648;\n\nvar growMemory = (size) => {\n  var b = wasmMemory.buffer;\n  var pages = (size - b.byteLength + 65535) / 65536;\n  try {\n    // round size grow request up to wasm page size (fixed 64KB per spec)\n    wasmMemory.grow(pages); // .grow() takes a delta compared to the previous size\n    updateMemoryViews();\n    return 1 /*success*/;\n  } catch (e) {\n    err(\n      `growMemory: Attempted to grow heap from ${b.byteLength} bytes to ${size} bytes, but got error: ${e}`\n    );\n  }\n  // implicit 0 return to save code size (caller will cast \"undefined\" into 0\n  // anyhow)\n};\nvar _emscripten_resize_heap = (requestedSize) => {\n  var oldSize = HEAPU8.length;\n  // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned.\n  requestedSize >>>= 0;\n  // With multithreaded builds, races can happen (another thread might increase the size\n  // in between), so return a failure, and let the caller retry.\n  assert(requestedSize > oldSize);\n\n  // Memory resize rules:\n  // 1.  Always increase heap size to at least the requested size, rounded up\n  //     to next page multiple.\n  // 2a. If MEMORY_GROWTH_LINEAR_STEP == -1, excessively resize the heap\n  //     geometrically: increase the heap size according to\n  //     MEMORY_GROWTH_GEOMETRIC_STEP factor (default +20%), At most\n  //     overreserve by MEMORY_GROWTH_GEOMETRIC_CAP bytes (default 96MB).\n  // 2b. If MEMORY_GROWTH_LINEAR_STEP != -1, excessively resize the heap\n  //     linearly: increase the heap size by at least\n  //     MEMORY_GROWTH_LINEAR_STEP bytes.\n  // 3.  Max size for the heap is capped at 2048MB-WASM_PAGE_SIZE, or by\n  //     MAXIMUM_MEMORY, or by ASAN limit, depending on which is smallest\n  // 4.  If we were unable to allocate as much memory, it may be due to\n  //     over-eager decision to excessively reserve due to (3) above.\n  //     Hence if an allocation fails, cut down on the amount of excess\n  //     growth, in an attempt to succeed to perform a smaller allocation.\n\n  // A limit is set for how much we can grow. We should not exceed that\n  // (the wasm binary specifies it, so if we tried, we'd fail anyhow).\n  var maxHeapSize = getHeapMax();\n  if (requestedSize > maxHeapSize) {\n    err(\n      `Cannot enlarge memory, requested ${requestedSize} bytes, but the limit is ${maxHeapSize} bytes!`\n    );\n    return false;\n  }\n\n  var alignUp = (x, multiple) => x + ((multiple - (x % multiple)) % multiple);\n\n  // Loop through potential heap size increases. If we attempt a too eager\n  // reservation that fails, cut down on the attempted size and reserve a\n  // smaller bump instead. (max 3 times, chosen somewhat arbitrarily)\n  for (var cutDown = 1; cutDown <= 4; cutDown *= 2) {\n    var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown); // ensure geometric growth\n    // but limit overreserving (default to capping at +96MB overgrowth at most)\n    overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296);\n\n    var newSize = Math.min(\n      maxHeapSize,\n      alignUp(Math.max(requestedSize, overGrownHeapSize), 65536)\n    );\n\n    var replacement = growMemory(newSize);\n    if (replacement) {\n      return true;\n    }\n  }\n  err(\n    `Failed to grow the heap from ${oldSize} bytes to ${newSize} bytes, not enough memory!`\n  );\n  return false;\n};\n\nvar ENV = {};\n\nvar getExecutableName = () => {\n  return thisProgram || './this.program';\n};\nvar getEnvStrings = () => {\n  if (!getEnvStrings.strings) {\n    // Default values.\n    // Browser language detection #8751\n    var lang =\n      (\n        (typeof navigator == 'object' &&\n          navigator.languages &&\n          navigator.languages[0]) ||\n        'C'\n      ).replace('-', '_') + '.UTF-8';\n    var env = {\n      USER: 'web_user',\n      LOGNAME: 'web_user',\n      PATH: '/',\n      PWD: '/',\n      HOME: '/home/web_user',\n      LANG: lang,\n      _: getExecutableName()\n    };\n    // Apply the user-provided values, if any.\n    for (var x in ENV) {\n      // x is a key in ENV; if ENV[x] is undefined, that means it was\n      // explicitly set to be so. We allow user code to do that to\n      // force variables with default values to remain unset.\n      if (ENV[x] === undefined) delete env[x];\n      else env[x] = ENV[x];\n    }\n    var strings = [];\n    for (var x in env) {\n      strings.push(`${x}=${env[x]}`);\n    }\n    getEnvStrings.strings = strings;\n  }\n  return getEnvStrings.strings;\n};\n\nvar stringToAscii = (str, buffer) => {\n  for (var i = 0; i < str.length; ++i) {\n    assert(str.charCodeAt(i) === (str.charCodeAt(i) & 0xff));\n    HEAP8[buffer++] = str.charCodeAt(i);\n  }\n  // Null-terminate the string\n  HEAP8[buffer] = 0;\n};\nvar _environ_get = (__environ, environ_buf) => {\n  var bufSize = 0;\n  getEnvStrings().forEach((string, i) => {\n    var ptr = environ_buf + bufSize;\n    HEAPU32[(__environ + i * 4) >> 2] = ptr;\n    stringToAscii(string, ptr);\n    bufSize += string.length + 1;\n  });\n  return 0;\n};\n\nvar _environ_sizes_get = (penviron_count, penviron_buf_size) => {\n  var strings = getEnvStrings();\n  HEAPU32[penviron_count >> 2] = strings.length;\n  var bufSize = 0;\n  strings.forEach((string) => (bufSize += string.length + 1));\n  HEAPU32[penviron_buf_size >> 2] = bufSize;\n  return 0;\n};\n\nvar runtimeKeepaliveCounter = 0;\nvar keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0;\nvar _proc_exit = (code) => {\n  EXITSTATUS = code;\n  if (!keepRuntimeAlive()) {\n    Module['onExit']?.(code);\n    ABORT = true;\n  }\n  quit_(code, new ExitStatus(code));\n};\n\n/** @suppress {duplicate } */\n/** @param {boolean|number=} implicit */\nvar exitJS = (status, implicit) => {\n  EXITSTATUS = status;\n\n  if (!keepRuntimeAlive()) {\n    exitRuntime();\n  }\n\n  // if exit() was called explicitly, warn the user if the runtime isn't actually being shut down\n  if (keepRuntimeAlive() && !implicit) {\n    var msg = `program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`;\n    err(msg);\n  }\n\n  _proc_exit(status);\n};\nvar _exit = exitJS;\n\nfunction _fd_close(fd) {\n  try {\n    var stream = SYSCALLS.getStreamFromFD(fd);\n    FS.close(stream);\n    return 0;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return e.errno;\n  }\n}\n\n/** @param {number=} offset */\nvar doReadv = (stream, iov, iovcnt, offset) => {\n  var ret = 0;\n  for (var i = 0; i < iovcnt; i++) {\n    var ptr = HEAPU32[iov >> 2];\n    var len = HEAPU32[(iov + 4) >> 2];\n    iov += 8;\n    var curr = FS.read(stream, HEAP8, ptr, len, offset);\n    if (curr < 0) return -1;\n    ret += curr;\n    if (curr < len) break; // nothing more to read\n    if (typeof offset !== 'undefined') {\n      offset += curr;\n    }\n  }\n  return ret;\n};\n\nfunction _fd_pread(fd, iov, iovcnt, offset_low, offset_high, pnum) {\n  var offset = convertI32PairToI53Checked(offset_low, offset_high);\n\n  try {\n    if (isNaN(offset)) return 61;\n    var stream = SYSCALLS.getStreamFromFD(fd);\n    var num = doReadv(stream, iov, iovcnt, offset);\n    HEAPU32[pnum >> 2] = num;\n    return 0;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return e.errno;\n  }\n}\n\n/** @param {number=} offset */\nvar doWritev = (stream, iov, iovcnt, offset) => {\n  var ret = 0;\n  for (var i = 0; i < iovcnt; i++) {\n    var ptr = HEAPU32[iov >> 2];\n    var len = HEAPU32[(iov + 4) >> 2];\n    iov += 8;\n    var curr = FS.write(stream, HEAP8, ptr, len, offset);\n    if (curr < 0) return -1;\n    ret += curr;\n    if (typeof offset !== 'undefined') {\n      offset += curr;\n    }\n  }\n  return ret;\n};\n\nfunction _fd_pwrite(fd, iov, iovcnt, offset_low, offset_high, pnum) {\n  var offset = convertI32PairToI53Checked(offset_low, offset_high);\n\n  try {\n    if (isNaN(offset)) return 61;\n    var stream = SYSCALLS.getStreamFromFD(fd);\n    var num = doWritev(stream, iov, iovcnt, offset);\n    HEAPU32[pnum >> 2] = num;\n    return 0;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return e.errno;\n  }\n}\n\nfunction _fd_read(fd, iov, iovcnt, pnum) {\n  try {\n    var stream = SYSCALLS.getStreamFromFD(fd);\n    var num = doReadv(stream, iov, iovcnt);\n    HEAPU32[pnum >> 2] = num;\n    return 0;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return e.errno;\n  }\n}\n\nfunction _fd_seek(fd, offset_low, offset_high, whence, newOffset) {\n  var offset = convertI32PairToI53Checked(offset_low, offset_high);\n\n  try {\n    if (isNaN(offset)) return 61;\n    var stream = SYSCALLS.getStreamFromFD(fd);\n    FS.llseek(stream, offset, whence);\n    (tempI64 = [\n      stream.position >>> 0,\n      ((tempDouble = stream.position),\n      +Math.abs(tempDouble) >= 1.0\n        ? tempDouble > 0.0\n          ? +Math.floor(tempDouble / 4294967296.0) >>> 0\n          : ~~+Math.ceil(\n              (tempDouble - +(~~tempDouble >>> 0)) / 4294967296.0\n            ) >>> 0\n        : 0)\n    ]),\n      (HEAP32[newOffset >> 2] = tempI64[0]),\n      (HEAP32[(newOffset + 4) >> 2] = tempI64[1]);\n    if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null; // reset readdir state\n    return 0;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return e.errno;\n  }\n}\n\nfunction _fd_write(fd, iov, iovcnt, pnum) {\n  try {\n    var stream = SYSCALLS.getStreamFromFD(fd);\n    var num = doWritev(stream, iov, iovcnt);\n    HEAPU32[pnum >> 2] = num;\n    return 0;\n  } catch (e) {\n    if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n    return e.errno;\n  }\n}\n\nvar handleException = (e) => {\n  // Certain exception types we do not treat as errors since they are used for\n  // internal control flow.\n  // 1. ExitStatus, which is thrown by exit()\n  // 2. \"unwind\", which is thrown by emscripten_unwind_to_js_event_loop() and others\n  //    that wish to return to JS event loop.\n  if (e instanceof ExitStatus || e == 'unwind') {\n    return EXITSTATUS;\n  }\n  checkStackCookie();\n  if (e instanceof WebAssembly.RuntimeError) {\n    if (_emscripten_stack_get_current() <= 0) {\n      err(\n        'Stack overflow detected.  You can try increasing -sSTACK_SIZE (currently set to 65536)'\n      );\n    }\n  }\n  quit_(1, e);\n};\n\nvar stringToUTF8OnStack = (str) => {\n  var size = lengthBytesUTF8(str) + 1;\n  var ret = stackAlloc(size);\n  stringToUTF8(str, ret, size);\n  return ret;\n};\n\nvar wasmTableMirror = [];\n\nvar wasmTable;\nvar getWasmTableEntry = (funcPtr) => {\n  var func = wasmTableMirror[funcPtr];\n  if (!func) {\n    if (funcPtr >= wasmTableMirror.length) wasmTableMirror.length = funcPtr + 1;\n    wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr);\n  }\n  assert(\n    wasmTable.get(funcPtr) == func,\n    'JavaScript-side Wasm function table mirror is out of date!'\n  );\n  return func;\n};\n\nFS.createPreloadedFile = FS_createPreloadedFile;\nFS.staticInit();\nfunction checkIncomingModuleAPI() {\n  ignoredModuleProp('fetchSettings');\n}\nvar wasmImports = {\n  /** @export */\n  __assert_fail: ___assert_fail,\n  /** @export */\n  __syscall_dup: ___syscall_dup,\n  /** @export */\n  __syscall_dup3: ___syscall_dup3,\n  /** @export */\n  __syscall_fcntl64: ___syscall_fcntl64,\n  /** @export */\n  __syscall_fstat64: ___syscall_fstat64,\n  /** @export */\n  __syscall_getdents64: ___syscall_getdents64,\n  /** @export */\n  __syscall_ioctl: ___syscall_ioctl,\n  /** @export */\n  __syscall_lstat64: ___syscall_lstat64,\n  /** @export */\n  __syscall_newfstatat: ___syscall_newfstatat,\n  /** @export */\n  __syscall_openat: ___syscall_openat,\n  /** @export */\n  __syscall_renameat: ___syscall_renameat,\n  /** @export */\n  __syscall_rmdir: ___syscall_rmdir,\n  /** @export */\n  __syscall_stat64: ___syscall_stat64,\n  /** @export */\n  __syscall_unlinkat: ___syscall_unlinkat,\n  /** @export */\n  _emscripten_get_now_is_monotonic: __emscripten_get_now_is_monotonic,\n  /** @export */\n  _emscripten_throw_longjmp: __emscripten_throw_longjmp,\n  /** @export */\n  _gmtime_js: __gmtime_js,\n  /** @export */\n  _localtime_js: __localtime_js,\n  /** @export */\n  _mktime_js: __mktime_js,\n  /** @export */\n  _tzset_js: __tzset_js,\n  /** @export */\n  emscripten_date_now: _emscripten_date_now,\n  /** @export */\n  emscripten_get_now: _emscripten_get_now,\n  /** @export */\n  emscripten_memcpy_js: _emscripten_memcpy_js,\n  /** @export */\n  emscripten_resize_heap: _emscripten_resize_heap,\n  /** @export */\n  environ_get: _environ_get,\n  /** @export */\n  environ_sizes_get: _environ_sizes_get,\n  /** @export */\n  exit: _exit,\n  /** @export */\n  fd_close: _fd_close,\n  /** @export */\n  fd_pread: _fd_pread,\n  /** @export */\n  fd_pwrite: _fd_pwrite,\n  /** @export */\n  fd_read: _fd_read,\n  /** @export */\n  fd_seek: _fd_seek,\n  /** @export */\n  fd_write: _fd_write,\n  /** @export */\n  invoke_ii: invoke_ii,\n  /** @export */\n  invoke_iii: invoke_iii,\n  /** @export */\n  invoke_iiii: invoke_iiii,\n  /** @export */\n  invoke_iiiii: invoke_iiiii,\n  /** @export */\n  invoke_vi: invoke_vi,\n  /** @export */\n  invoke_vii: invoke_vii,\n  /** @export */\n  invoke_viii: invoke_viii,\n  /** @export */\n  invoke_viiii: invoke_viiii\n};\nvar wasmExports = createWasm();\nvar ___wasm_call_ctors = createExportWrapper('__wasm_call_ctors');\nvar _main = (Module['_main'] = createExportWrapper('__main_argc_argv'));\nvar _malloc = createExportWrapper('malloc');\nvar setTempRet0 = createExportWrapper('setTempRet0');\nvar _free = createExportWrapper('free');\nvar _fflush = createExportWrapper('fflush');\nvar ___funcs_on_exit = createExportWrapper('__funcs_on_exit');\nvar _setThrew = createExportWrapper('setThrew');\nvar _emscripten_stack_init = () =>\n  (_emscripten_stack_init = wasmExports['emscripten_stack_init'])();\nvar _emscripten_stack_get_free = () =>\n  (_emscripten_stack_get_free = wasmExports['emscripten_stack_get_free'])();\nvar _emscripten_stack_get_base = () =>\n  (_emscripten_stack_get_base = wasmExports['emscripten_stack_get_base'])();\nvar _emscripten_stack_get_end = () =>\n  (_emscripten_stack_get_end = wasmExports['emscripten_stack_get_end'])();\nvar stackSave = createExportWrapper('stackSave');\nvar stackRestore = createExportWrapper('stackRestore');\nvar stackAlloc = createExportWrapper('stackAlloc');\nvar _emscripten_stack_get_current = () =>\n  (_emscripten_stack_get_current =\n    wasmExports['emscripten_stack_get_current'])();\nvar dynCall_jii = (Module['dynCall_jii'] = createExportWrapper('dynCall_jii'));\nvar dynCall_iiji = (Module['dynCall_iiji'] =\n  createExportWrapper('dynCall_iiji'));\nvar dynCall_iiiiiij = (Module['dynCall_iiiiiij'] =\n  createExportWrapper('dynCall_iiiiiij'));\nvar dynCall_iiiiiiijjii = (Module['dynCall_iiiiiiijjii'] = createExportWrapper(\n  'dynCall_iiiiiiijjii'\n));\nvar dynCall_iiiiiiiiiijj = (Module['dynCall_iiiiiiiiiijj'] =\n  createExportWrapper('dynCall_iiiiiiiiiijj'));\nvar dynCall_jiiiii = (Module['dynCall_jiiiii'] =\n  createExportWrapper('dynCall_jiiiii'));\nvar dynCall_iiiiiiiiiiji = (Module['dynCall_iiiiiiiiiiji'] =\n  createExportWrapper('dynCall_iiiiiiiiiiji'));\nvar dynCall_iiiijii = (Module['dynCall_iiiijii'] =\n  createExportWrapper('dynCall_iiiijii'));\nvar dynCall_iiiiijiii = (Module['dynCall_iiiiijiii'] =\n  createExportWrapper('dynCall_iiiiijiii'));\nvar dynCall_viiiiiiiiiiiiiiiiiiiiiiiiiiiiiiijiiiiii = (Module[\n  'dynCall_viiiiiiiiiiiiiiiiiiiiiiiiiiiiiiijiiiiii'\n] = createExportWrapper('dynCall_viiiiiiiiiiiiiiiiiiiiiiiiiiiiiiijiiiiii'));\nvar dynCall_viiiiiiiiiiiiiijiiiii = (Module['dynCall_viiiiiiiiiiiiiijiiiii'] =\n  createExportWrapper('dynCall_viiiiiiiiiiiiiijiiiii'));\nvar dynCall_vijii = (Module['dynCall_vijii'] =\n  createExportWrapper('dynCall_vijii'));\nvar dynCall_jji = (Module['dynCall_jji'] = createExportWrapper('dynCall_jji'));\nvar dynCall_iji = (Module['dynCall_iji'] = createExportWrapper('dynCall_iji'));\nvar dynCall_jiji = (Module['dynCall_jiji'] =\n  createExportWrapper('dynCall_jiji'));\nvar dynCall_iij = (Module['dynCall_iij'] = createExportWrapper('dynCall_iij'));\nvar dynCall_ji = (Module['dynCall_ji'] = createExportWrapper('dynCall_ji'));\nvar dynCall_iijii = (Module['dynCall_iijii'] =\n  createExportWrapper('dynCall_iijii'));\nvar dynCall_iijj = (Module['dynCall_iijj'] =\n  createExportWrapper('dynCall_iijj'));\nvar dynCall_iijjjjjj = (Module['dynCall_iijjjjjj'] =\n  createExportWrapper('dynCall_iijjjjjj'));\nvar dynCall_viiiiiiiiijiiii = (Module['dynCall_viiiiiiiiijiiii'] =\n  createExportWrapper('dynCall_viiiiiiiiijiiii'));\n\nfunction invoke_vi(index, a1) {\n  var sp = stackSave();\n  try {\n    getWasmTableEntry(index)(a1);\n  } catch (e) {\n    stackRestore(sp);\n    if (e !== e + 0) throw e;\n    _setThrew(1, 0);\n  }\n}\n\nfunction invoke_ii(index, a1) {\n  var sp = stackSave();\n  try {\n    return getWasmTableEntry(index)(a1);\n  } catch (e) {\n    stackRestore(sp);\n    if (e !== e + 0) throw e;\n    _setThrew(1, 0);\n  }\n}\n\nfunction invoke_vii(index, a1, a2) {\n  var sp = stackSave();\n  try {\n    getWasmTableEntry(index)(a1, a2);\n  } catch (e) {\n    stackRestore(sp);\n    if (e !== e + 0) throw e;\n    _setThrew(1, 0);\n  }\n}\n\nfunction invoke_iii(index, a1, a2) {\n  var sp = stackSave();\n  try {\n    return getWasmTableEntry(index)(a1, a2);\n  } catch (e) {\n    stackRestore(sp);\n    if (e !== e + 0) throw e;\n    _setThrew(1, 0);\n  }\n}\n\nfunction invoke_viii(index, a1, a2, a3) {\n  var sp = stackSave();\n  try {\n    getWasmTableEntry(index)(a1, a2, a3);\n  } catch (e) {\n    stackRestore(sp);\n    if (e !== e + 0) throw e;\n    _setThrew(1, 0);\n  }\n}\n\nfunction invoke_iiii(index, a1, a2, a3) {\n  var sp = stackSave();\n  try {\n    return getWasmTableEntry(index)(a1, a2, a3);\n  } catch (e) {\n    stackRestore(sp);\n    if (e !== e + 0) throw e;\n    _setThrew(1, 0);\n  }\n}\n\nfunction invoke_viiii(index, a1, a2, a3, a4) {\n  var sp = stackSave();\n  try {\n    getWasmTableEntry(index)(a1, a2, a3, a4);\n  } catch (e) {\n    stackRestore(sp);\n    if (e !== e + 0) throw e;\n    _setThrew(1, 0);\n  }\n}\n\nfunction invoke_iiiii(index, a1, a2, a3, a4) {\n  var sp = stackSave();\n  try {\n    return getWasmTableEntry(index)(a1, a2, a3, a4);\n  } catch (e) {\n    stackRestore(sp);\n    if (e !== e + 0) throw e;\n    _setThrew(1, 0);\n  }\n}\n\n// include: postamble.js\n// === Auto-generated postamble setup entry stuff ===\n\nModule['FS'] = FS;\nModule['run'] = run;\nvar missingLibrarySymbols = [\n  'writeI53ToI64',\n  'writeI53ToI64Clamped',\n  'writeI53ToI64Signaling',\n  'writeI53ToU64Clamped',\n  'writeI53ToU64Signaling',\n  'readI53FromI64',\n  'readI53FromU64',\n  'convertI32PairToI53',\n  'convertU32PairToI53',\n  'arraySum',\n  'addDays',\n  'inetPton4',\n  'inetNtop4',\n  'inetPton6',\n  'inetNtop6',\n  'readSockaddr',\n  'writeSockaddr',\n  'getCallstack',\n  'emscriptenLog',\n  'convertPCtoSourceLocation',\n  'readEmAsmArgs',\n  'jstoi_q',\n  'listenOnce',\n  'autoResumeAudioContext',\n  'dynCallLegacy',\n  'getDynCaller',\n  'dynCall',\n  'runtimeKeepalivePush',\n  'runtimeKeepalivePop',\n  'callUserCallback',\n  'maybeExit',\n  'asmjsMangle',\n  'HandleAllocator',\n  'getNativeTypeSize',\n  'STACK_SIZE',\n  'STACK_ALIGN',\n  'POINTER_SIZE',\n  'ASSERTIONS',\n  'getCFunc',\n  'ccall',\n  'cwrap',\n  'uleb128Encode',\n  'sigToWasmTypes',\n  'generateFuncType',\n  'convertJsFunctionToWasm',\n  'getEmptyTableSlot',\n  'updateTableMap',\n  'getFunctionAddress',\n  'addFunction',\n  'removeFunction',\n  'reallyNegative',\n  'unSign',\n  'strLen',\n  'reSign',\n  'formatString',\n  'intArrayToString',\n  'AsciiToString',\n  'UTF16ToString',\n  'stringToUTF16',\n  'lengthBytesUTF16',\n  'UTF32ToString',\n  'stringToUTF32',\n  'lengthBytesUTF32',\n  'stringToNewUTF8',\n  'writeArrayToMemory',\n  'registerKeyEventCallback',\n  'maybeCStringToJsString',\n  'findEventTarget',\n  'getBoundingClientRect',\n  'fillMouseEventData',\n  'registerMouseEventCallback',\n  'registerWheelEventCallback',\n  'registerUiEventCallback',\n  'registerFocusEventCallback',\n  'fillDeviceOrientationEventData',\n  'registerDeviceOrientationEventCallback',\n  'fillDeviceMotionEventData',\n  'registerDeviceMotionEventCallback',\n  'screenOrientation',\n  'fillOrientationChangeEventData',\n  'registerOrientationChangeEventCallback',\n  'fillFullscreenChangeEventData',\n  'registerFullscreenChangeEventCallback',\n  'JSEvents_requestFullscreen',\n  'JSEvents_resizeCanvasForFullscreen',\n  'registerRestoreOldStyle',\n  'hideEverythingExceptGivenElement',\n  'restoreHiddenElements',\n  'setLetterbox',\n  'softFullscreenResizeWebGLRenderTarget',\n  'doRequestFullscreen',\n  'fillPointerlockChangeEventData',\n  'registerPointerlockChangeEventCallback',\n  'registerPointerlockErrorEventCallback',\n  'requestPointerLock',\n  'fillVisibilityChangeEventData',\n  'registerVisibilityChangeEventCallback',\n  'registerTouchEventCallback',\n  'fillGamepadEventData',\n  'registerGamepadEventCallback',\n  'registerBeforeUnloadEventCallback',\n  'fillBatteryEventData',\n  'battery',\n  'registerBatteryEventCallback',\n  'setCanvasElementSize',\n  'getCanvasElementSize',\n  'jsStackTrace',\n  'stackTrace',\n  'checkWasiClock',\n  'wasiRightsToMuslOFlags',\n  'wasiOFlagsToMuslOFlags',\n  'createDyncallWrapper',\n  'safeSetTimeout',\n  'setImmediateWrapped',\n  'clearImmediateWrapped',\n  'polyfillSetImmediate',\n  'getPromise',\n  'makePromise',\n  'idsToPromises',\n  'makePromiseCallback',\n  'ExceptionInfo',\n  'findMatchingCatch',\n  'Browser_asyncPrepareDataCounter',\n  'setMainLoop',\n  'getSocketFromFD',\n  'getSocketAddress',\n  'FS_unlink',\n  'FS_mkdirTree',\n  '_setNetworkCallback',\n  'heapObjectForWebGLType',\n  'toTypedArrayIndex',\n  'webgl_enable_ANGLE_instanced_arrays',\n  'webgl_enable_OES_vertex_array_object',\n  'webgl_enable_WEBGL_draw_buffers',\n  'webgl_enable_WEBGL_multi_draw',\n  'emscriptenWebGLGet',\n  'computeUnpackAlignedImageSize',\n  'colorChannelsInGlTextureFormat',\n  'emscriptenWebGLGetTexPixelData',\n  'emscriptenWebGLGetUniform',\n  'webglGetUniformLocation',\n  'webglPrepareUniformLocationsBeforeFirstUse',\n  'webglGetLeftBracePos',\n  'emscriptenWebGLGetVertexAttrib',\n  '__glGetActiveAttribOrUniform',\n  'writeGLArray',\n  'registerWebGlEventCallback',\n  'runAndAbortIfError',\n  'ALLOC_NORMAL',\n  'ALLOC_STACK',\n  'allocate',\n  'writeStringToMemory',\n  'writeAsciiToMemory',\n  'setErrNo',\n  'demangle'\n];\nmissingLibrarySymbols.forEach(missingLibrarySymbol);\n\nvar unexportedSymbols = [\n  'run',\n  'addOnPreRun',\n  'addOnInit',\n  'addOnPreMain',\n  'addOnExit',\n  'addOnPostRun',\n  'addRunDependency',\n  'removeRunDependency',\n  'FS_createFolder',\n  'FS_createPath',\n  'FS_createLazyFile',\n  'FS_createLink',\n  'FS_createDevice',\n  'FS_readFile',\n  'out',\n  'err',\n  'callMain',\n  'abort',\n  'wasmMemory',\n  'wasmExports',\n  'stackAlloc',\n  'stackSave',\n  'stackRestore',\n  'getTempRet0',\n  'setTempRet0',\n  'writeStackCookie',\n  'checkStackCookie',\n  'convertI32PairToI53Checked',\n  'ptrToString',\n  'zeroMemory',\n  'exitJS',\n  'getHeapMax',\n  'growMemory',\n  'ENV',\n  'MONTH_DAYS_REGULAR',\n  'MONTH_DAYS_LEAP',\n  'MONTH_DAYS_REGULAR_CUMULATIVE',\n  'MONTH_DAYS_LEAP_CUMULATIVE',\n  'isLeapYear',\n  'ydayFromDate',\n  'ERRNO_CODES',\n  'ERRNO_MESSAGES',\n  'DNS',\n  'Protocols',\n  'Sockets',\n  'initRandomFill',\n  'randomFill',\n  'timers',\n  'warnOnce',\n  'UNWIND_CACHE',\n  'readEmAsmArgsArray',\n  'jstoi_s',\n  'getExecutableName',\n  'handleException',\n  'keepRuntimeAlive',\n  'asyncLoad',\n  'alignMemory',\n  'mmapAlloc',\n  'wasmTable',\n  'noExitRuntime',\n  'freeTableIndexes',\n  'functionsInTableMap',\n  'setValue',\n  'getValue',\n  'PATH',\n  'PATH_FS',\n  'UTF8Decoder',\n  'UTF8ArrayToString',\n  'UTF8ToString',\n  'stringToUTF8Array',\n  'stringToUTF8',\n  'lengthBytesUTF8',\n  'intArrayFromString',\n  'stringToAscii',\n  'UTF16Decoder',\n  'stringToUTF8OnStack',\n  'JSEvents',\n  'specialHTMLTargets',\n  'findCanvasEventTarget',\n  'currentFullscreenStrategy',\n  'restoreOldWindowedStyle',\n  'ExitStatus',\n  'getEnvStrings',\n  'doReadv',\n  'doWritev',\n  'promiseMap',\n  'uncaughtExceptionCount',\n  'exceptionLast',\n  'exceptionCaught',\n  'Browser',\n  'getPreloadedImageData__data',\n  'wget',\n  'SYSCALLS',\n  'preloadPlugins',\n  'FS_createPreloadedFile',\n  'FS_modeStringToFlags',\n  'FS_getMode',\n  'FS_stdin_getChar_buffer',\n  'FS_stdin_getChar',\n  'FS',\n  'FS_createDataFile',\n  'MEMFS',\n  'TTY',\n  'PIPEFS',\n  'SOCKFS',\n  'tempFixedLengthArray',\n  'miniTempWebGLFloatBuffers',\n  'miniTempWebGLIntBuffers',\n  'GL',\n  'AL',\n  'GLUT',\n  'EGL',\n  'GLEW',\n  'IDBStore',\n  'SDL',\n  'SDL_gfx',\n  'allocateUTF8',\n  'allocateUTF8OnStack'\n];\nunexportedSymbols.forEach(unexportedRuntimeSymbol);\n\nvar calledRun;\n\ndependenciesFulfilled = function runCaller() {\n  // If run has never been called, and we should call run (INVOKE_RUN is true, and Module.noInitialRun is not false)\n  if (!calledRun) run();\n  if (!calledRun) dependenciesFulfilled = runCaller; // try this again later, after new deps are fulfilled\n};\n\nfunction callMain(args = []) {\n  assert(\n    runDependencies == 0,\n    'cannot call main when async dependencies remain! (listen on Module[\"onRuntimeInitialized\"])'\n  );\n  assert(\n    __ATPRERUN__.length == 0,\n    'cannot call main when preRun functions remain to be called'\n  );\n\n  var entryFunction = _main;\n\n  args.unshift(thisProgram);\n\n  var argc = args.length;\n  var argv = stackAlloc((argc + 1) * 4);\n  var argv_ptr = argv;\n  args.forEach((arg) => {\n    HEAPU32[argv_ptr >> 2] = stringToUTF8OnStack(arg);\n    argv_ptr += 4;\n  });\n  HEAPU32[argv_ptr >> 2] = 0;\n\n  try {\n    var ret = entryFunction(argc, argv);\n\n    // if we're not running an evented main loop, it's time to exit\n    exitJS(ret, /* implicit = */ true);\n    return ret;\n  } catch (e) {\n    return handleException(e);\n  }\n}\n\nfunction stackCheckInit() {\n  // This is normally called automatically during __wasm_call_ctors but need to\n  // get these values before even running any of the ctors so we call it redundantly\n  // here.\n  _emscripten_stack_init();\n  // TODO(sbc): Move writeStackCookie to native to to avoid this.\n  writeStackCookie();\n}\n\nfunction run(args = arguments_) {\n  if (runDependencies > 0) {\n    return;\n  }\n\n  stackCheckInit();\n\n  preRun();\n\n  // a preRun added a dependency, run will be called later\n  if (runDependencies > 0) {\n    return;\n  }\n\n  function doRun() {\n    // run may have just been called through dependencies being fulfilled just in this very frame,\n    // or while the async setStatus time below was happening\n    if (calledRun) return;\n    calledRun = true;\n    Module['calledRun'] = true;\n\n    if (ABORT) return;\n\n    initRuntime();\n\n    preMain();\n\n    if (Module['onRuntimeInitialized']) Module['onRuntimeInitialized']();\n\n    if (shouldRunNow) callMain(args);\n\n    postRun();\n  }\n\n  if (Module['setStatus']) {\n    Module['setStatus']('Running...');\n    setTimeout(function () {\n      setTimeout(function () {\n        Module['setStatus']('');\n      }, 1);\n      doRun();\n    }, 1);\n  } else {\n    doRun();\n  }\n  checkStackCookie();\n}\n\nif (Module['preInit']) {\n  if (typeof Module['preInit'] == 'function')\n    Module['preInit'] = [Module['preInit']];\n  while (Module['preInit'].length > 0) {\n    Module['preInit'].pop()();\n  }\n}\n\n// shouldRunNow refers to calling main(), not run().\nvar shouldRunNow = true;\n\nif (Module['noInitialRun']) shouldRunNow = false;\n\nrun();\n\nvar workerResponded = false,\n  workerCallbackId = -1;\n\n(function () {\n  var messageBuffer = null,\n    buffer = 0,\n    bufferSize = 0;\n\n  function flushMessages() {\n    if (!messageBuffer) return;\n    if (runtimeInitialized) {\n      var temp = messageBuffer;\n      messageBuffer = null;\n      temp.forEach(function (message) {\n        onmessage(message);\n      });\n    }\n  }\n\n  function messageResender() {\n    flushMessages();\n    if (messageBuffer) {\n      setTimeout(messageResender, 100); // still more to do\n    }\n  }\n\n  onmessage = (msg) => {\n    // if main has not yet been called (mem init file, other async things), buffer messages\n    if (!runtimeInitialized) {\n      if (!messageBuffer) {\n        messageBuffer = [];\n        setTimeout(messageResender, 100);\n      }\n      messageBuffer.push(msg);\n      return;\n    }\n    flushMessages();\n\n    var func = Module['_' + msg.data['funcName']];\n    if (!func) throw 'invalid worker function to call: ' + msg.data['funcName'];\n    var data = msg.data['data'];\n    if (data) {\n      if (!data.byteLength) data = new Uint8Array(data);\n      if (!buffer || bufferSize < data.length) {\n        if (buffer) _free(buffer);\n        bufferSize = data.length;\n        buffer = _malloc(data.length);\n      }\n      HEAPU8.set(data, buffer);\n    }\n\n    workerResponded = false;\n    workerCallbackId = msg.data['callbackId'];\n    if (data) {\n      func(buffer, data.length);\n    } else {\n      func(0, 0);\n    }\n  };\n})();\n\n// end include: postamble.js\n"
  },
  {
    "path": "src/lib/ghostscript/worker-init.ts",
    "content": "export const COMPRESS_ACTION = 'compress-pdf';\nexport const PROTECT_ACTION = 'protect-pdf';\n\nexport async function compressWithGhostScript(dataStruct: {\n  psDataURL: string;\n}): Promise<string> {\n  const worker = getWorker();\n  worker.postMessage({\n    data: { ...dataStruct, type: COMPRESS_ACTION },\n    target: 'wasm'\n  });\n  return getListener(worker);\n}\n\nexport async function protectWithGhostScript(dataStruct: {\n  psDataURL: string;\n}): Promise<string> {\n  const worker = getWorker();\n  worker.postMessage({\n    data: { ...dataStruct, type: PROTECT_ACTION },\n    target: 'wasm'\n  });\n  return getListener(worker);\n}\n\nconst getListener = (worker: Worker): Promise<string> => {\n  return new Promise((resolve, reject) => {\n    const listener = (e: MessageEvent) => {\n      resolve(e.data);\n      worker.removeEventListener('message', listener);\n      setTimeout(() => worker.terminate(), 0);\n    };\n    worker.addEventListener('message', listener);\n  });\n};\n\nconst getWorker = () => {\n  return new Worker(new URL('./background-worker.js', import.meta.url), {\n    type: 'module'\n  });\n};\n"
  },
  {
    "path": "src/pages/home/Categories.tsx",
    "content": "import { getToolsByCategory } from '@tools/index';\nimport Grid from '@mui/material/Grid';\nimport { Box, Card, CardContent, Stack, useTheme } from '@mui/material';\nimport { Link, useNavigate } from 'react-router-dom';\nimport Typography from '@mui/material/Typography';\nimport Button from '@mui/material/Button';\nimport { useState } from 'react';\nimport { categoriesColors } from 'config/uiConfig';\nimport { Icon } from '@iconify/react';\nimport { useTranslation } from 'react-i18next';\nimport { getI18nNamespaceFromToolCategory } from '@utils/string';\nimport { useUserTypeFilter } from '../../providers/UserTypeFilterProvider';\n\ntype ArrayElement<ArrayType extends readonly unknown[]> =\n  ArrayType extends readonly (infer ElementType)[] ? ElementType : never;\n\nconst SingleCategory = function ({\n  category,\n  index\n}: {\n  category: ArrayElement<ReturnType<typeof getToolsByCategory>>;\n  index: number;\n}) {\n  const { t } = useTranslation(getI18nNamespaceFromToolCategory(category.type));\n  const navigate = useNavigate();\n  const theme = useTheme();\n  const [hovered, setHovered] = useState<boolean>(false);\n  const toggleHover = () => setHovered((prevState) => !prevState);\n\n  // Get translated category title and description\n  const categoryTitle = t(`categories.${category.type}.title`, category.title);\n  const categoryDescription = t(\n    `categories.${category.type}.description`,\n    category.description\n  );\n  const seeAllText = t('translation:categories.seeAll', 'See all {{title}}', {\n    title: categoryTitle\n  });\n  const tryText = t('translation:categories.try', 'Try {{title}}', {\n    //@ts-ignore\n    title: t(category.example.title)\n  });\n\n  return (\n    <Grid\n      item\n      xs={12}\n      md={6}\n      onMouseEnter={toggleHover}\n      onMouseLeave={toggleHover}\n    >\n      <Card\n        sx={{\n          height: '100%',\n          backgroundColor: hovered ? 'background.hover' : 'background.paper'\n        }}\n      >\n        <CardContent sx={{ height: '100%' }}>\n          <Stack\n            direction={'column'}\n            height={'100%'}\n            justifyContent={'space-between'}\n          >\n            <Box>\n              <Stack direction={'row'} spacing={2} alignItems={'center'}>\n                <Icon\n                  icon={category.icon}\n                  fontSize={'60px'}\n                  style={{\n                    transform: `scale(${hovered ? 1.1 : 1}`\n                  }}\n                  color={categoriesColors[index % categoriesColors.length]}\n                />\n                <Link\n                  style={{\n                    fontSize: 20,\n                    fontWeight: 700,\n                    color: theme.palette.mode === 'dark' ? 'white' : 'black'\n                  }}\n                  to={'/categories/' + category.type}\n                >\n                  {categoryTitle}\n                </Link>\n              </Stack>\n              <Typography sx={{ mt: 2 }}>{categoryDescription}</Typography>\n            </Box>\n            <Grid container spacing={2} mt={2}>\n              <Grid item xs={12} md={6}>\n                <Button\n                  fullWidth\n                  sx={{ height: '100%' }}\n                  onClick={() => navigate('/categories/' + category.type)}\n                  variant={'contained'}\n                >\n                  {seeAllText}\n                </Button>\n              </Grid>\n              <Grid item xs={12} md={6}>\n                <Button\n                  sx={{ backgroundColor: 'background.default', height: '100%' }}\n                  fullWidth\n                  onClick={() => navigate(category.example.path)}\n                  variant={'outlined'}\n                >\n                  {tryText}\n                </Button>\n              </Grid>\n            </Grid>\n          </Stack>\n        </CardContent>\n      </Card>\n    </Grid>\n  );\n};\n\nexport default function Categories() {\n  const { selectedUserTypes } = useUserTypeFilter();\n  const { t } = useTranslation();\n  const categories = getToolsByCategory(selectedUserTypes, t);\n\n  return (\n    <Grid width={'80%'} container spacing={2}>\n      {categories.map((category, index) => (\n        <SingleCategory key={category.type} category={category} index={index} />\n      ))}\n    </Grid>\n  );\n}\n"
  },
  {
    "path": "src/pages/home/index.tsx",
    "content": "import { Box, useTheme } from '@mui/material';\nimport Hero from 'components/Hero';\nimport Categories from './Categories';\nimport { Helmet } from 'react-helmet';\nimport { useUserTypeFilter } from 'providers/UserTypeFilterProvider';\nimport UserTypeFilter from '@components/UserTypeFilter';\n\nexport default function Home() {\n  const theme = useTheme();\n  const { selectedUserTypes, setSelectedUserTypes } = useUserTypeFilter();\n  return (\n    <Box\n      padding={{\n        xs: 1,\n        md: 3,\n        lg: 5\n      }}\n      sx={{\n        background: `url(/assets/${\n          theme.palette.mode === 'dark'\n            ? 'background-dark.png'\n            : 'background.svg'\n        })`,\n        backgroundColor: 'background.default'\n      }}\n      display={'flex'}\n      flexDirection={'column'}\n      alignItems={'center'}\n      justifyContent={'center'}\n      width={'100%'}\n    >\n      <Helmet title={'OmniTools'} />\n      <Hero />\n      <Box my={3}>\n        <UserTypeFilter\n          selectedUserTypes={selectedUserTypes}\n          onUserTypesChange={setSelectedUserTypes}\n        />\n      </Box>\n      <Categories />\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/audio/change-speed/change-speed.service.test.ts",
    "content": "import { expect, describe, it, vi } from 'vitest';\n\n// Mock FFmpeg since it doesn't support Node.js\nvi.mock('@ffmpeg/ffmpeg', () => ({\n  FFmpeg: vi.fn().mockImplementation(() => ({\n    loaded: false,\n    load: vi.fn().mockResolvedValue(undefined),\n    writeFile: vi.fn().mockResolvedValue(undefined),\n    exec: vi.fn().mockResolvedValue(undefined),\n    readFile: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4, 5])),\n    deleteFile: vi.fn().mockResolvedValue(undefined)\n  }))\n}));\n\nvi.mock('@ffmpeg/util', () => ({\n  fetchFile: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4, 5]))\n}));\n\nimport { changeAudioSpeed } from './service';\nimport { InitialValuesType } from './types';\n\ndescribe('changeAudioSpeed', () => {\n  it('should return a new File with the correct name and type', async () => {\n    const mockAudioData = new Uint8Array([0, 1, 2, 3, 4, 5]);\n    const mockFile = new File([mockAudioData], 'test.mp3', {\n      type: 'audio/mp3'\n    });\n    const options: InitialValuesType = {\n      newSpeed: 2,\n      outputFormat: 'mp3'\n    };\n    const result = await changeAudioSpeed(mockFile, options);\n    expect(result).toBeInstanceOf(File);\n    expect(result?.name).toBe('test-2x.mp3');\n    expect(result?.type).toBe('audio/mp3');\n  });\n\n  it('should return null if input is null', async () => {\n    const options: InitialValuesType = {\n      newSpeed: 2,\n      outputFormat: 'mp3'\n    };\n    const result = await changeAudioSpeed(null, options);\n    expect(result).toBeNull();\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/audio/change-speed/index.tsx",
    "content": "import { Box, FormControlLabel, Radio, RadioGroup } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { InitialValuesType } from './types';\nimport ToolAudioInput from '@components/input/ToolAudioInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport RadioWithTextField from '@components/options/RadioWithTextField';\nimport { changeAudioSpeed } from './service';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  newSpeed: 2,\n  outputFormat: 'mp3'\n};\n\nconst formatOptions = [\n  { label: 'MP3', value: 'mp3' },\n  { label: 'AAC', value: 'aac' },\n  { label: 'WAV', value: 'wav' }\n];\n\nexport default function ChangeSpeed({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('audio');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const compute = async (\n    optionsValues: InitialValuesType,\n    input: File | null\n  ) => {\n    setLoading(true);\n    try {\n      const newFile = await changeAudioSpeed(input, optionsValues);\n      setResult(newFile);\n    } catch (err) {\n      setResult(null);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('changeSpeed.newAudioSpeed'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.newSpeed.toString()}\n            onOwnChange={(val) => updateField('newSpeed', Number(val))}\n            description={t('changeSpeed.speedDescription')}\n            type=\"number\"\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('changeSpeed.outputFormat'),\n      component: (\n        <Box mt={2}>\n          <RadioGroup\n            row\n            value={values.outputFormat}\n            onChange={(e) =>\n              updateField(\n                'outputFormat',\n                e.target.value as 'mp3' | 'aac' | 'wav'\n              )\n            }\n          >\n            {formatOptions.map((opt) => (\n              <FormControlLabel\n                key={opt.value}\n                value={opt.value}\n                control={<Radio />}\n                label={opt.label}\n              />\n            ))}\n          </RadioGroup>\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolAudioInput\n          value={input}\n          onChange={setInput}\n          title={t('changeSpeed.inputTitle')}\n        />\n      }\n      resultComponent={\n        loading ? (\n          <ToolFileResult\n            title={t('changeSpeed.settingSpeed')}\n            value={null}\n            loading={true}\n          />\n        ) : (\n          <ToolFileResult\n            title={t('changeSpeed.resultTitle')}\n            value={result}\n            extension={result ? result.name.split('.').pop() : undefined}\n          />\n        )\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('changeSpeed.toolInfo.title', { title }),\n        description: longDescription\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/audio/change-speed/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('audio', {\n  path: 'change-speed',\n  icon: 'material-symbols:speed',\n\n  keywords: [\n    'audio',\n    'speed',\n    'tempo',\n    'playback',\n    'accelerate',\n    'slow down',\n    'pitch',\n    'media'\n  ],\n\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'audio:changeSpeed.title',\n    description: 'audio:changeSpeed.description',\n    shortDescription: 'audio:changeSpeed.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/audio/change-speed/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\n\nfunction computeAudioFilter(speed: number): string {\n  if (speed <= 2 && speed >= 0.5) {\n    return `atempo=${speed}`;\n  }\n  const filters: string[] = [];\n  let remainingSpeed = speed;\n  while (remainingSpeed > 2.0) {\n    filters.push('atempo=2.0');\n    remainingSpeed /= 2.0;\n  }\n  while (remainingSpeed < 0.5) {\n    filters.push('atempo=0.5');\n    remainingSpeed /= 0.5;\n  }\n  filters.push(`atempo=${remainingSpeed.toFixed(2)}`);\n  return filters.join(',');\n}\n\nexport async function changeAudioSpeed(\n  input: File | null,\n  options: InitialValuesType\n): Promise<File | null> {\n  if (!input) return null;\n  const { newSpeed, outputFormat } = options;\n  let ffmpeg: FFmpeg | null = null;\n  let ffmpegLoaded = false;\n  try {\n    ffmpeg = new FFmpeg();\n    if (!ffmpegLoaded) {\n      await ffmpeg.load({\n        wasmURL:\n          'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n      });\n      ffmpegLoaded = true;\n    }\n    const fileName = input.name;\n    const outputName = `output.${outputFormat}`;\n    await ffmpeg.writeFile(fileName, await fetchFile(input));\n    const audioFilter = computeAudioFilter(newSpeed);\n    const args = ['-i', fileName, '-filter:a', audioFilter];\n    if (outputFormat === 'mp3') {\n      args.push('-b:a', '192k', '-f', 'mp3', outputName);\n    } else if (outputFormat === 'aac') {\n      args.push('-c:a', 'aac', '-b:a', '192k', '-f', 'adts', outputName);\n    } else if (outputFormat === 'wav') {\n      args.push(\n        '-acodec',\n        'pcm_s16le',\n        '-ar',\n        '44100',\n        '-ac',\n        '2',\n        '-f',\n        'wav',\n        outputName\n      );\n    }\n    await ffmpeg.exec(args);\n    const data = await ffmpeg.readFile(outputName);\n    let mimeType = 'audio/mp3';\n    if (outputFormat === 'aac') mimeType = 'audio/aac';\n    if (outputFormat === 'wav') mimeType = 'audio/wav';\n    const blob = new Blob([data as any], { type: mimeType });\n    const newFile = new File(\n      [blob],\n      fileName.replace(/\\.[^/.]+$/, `-${newSpeed}x.${outputFormat}`),\n      { type: mimeType }\n    );\n    await ffmpeg.deleteFile(fileName);\n    await ffmpeg.deleteFile(outputName);\n    return newFile;\n  } catch (err) {\n    console.error(`Failed to process audio: ${err}`);\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/pages/tools/audio/change-speed/types.ts",
    "content": "export type InitialValuesType = {\n  newSpeed: number;\n  outputFormat: 'mp3' | 'aac' | 'wav';\n};\n"
  },
  {
    "path": "src/pages/tools/audio/extract-audio/extract-audio.service.test.ts",
    "content": "import { describe, it, expect, vi, beforeAll } from 'vitest';\n\n// Mock the service module BEFORE importing it\nvi.mock('./service', () => ({\n  extractAudioFromVideo: vi.fn(async (input, options) => {\n    const ext = options.outputFormat;\n    return new File([new Blob(['audio data'])], `mock_audio.${ext}`, {\n      type: `audio/${ext}`\n    });\n  })\n}));\n\nimport { extractAudioFromVideo } from './service';\nimport { InitialValuesType } from './types';\n\nfunction createMockVideoFile(): File {\n  return new File(['video data'], 'test.mp4', { type: 'video/mp4' });\n}\n\ndescribe('extractAudioFromVideo (mocked)', () => {\n  let videoFile: File;\n\n  beforeAll(() => {\n    videoFile = createMockVideoFile();\n  });\n\n  it('should extract audio as AAC', async () => {\n    const options: InitialValuesType = { outputFormat: 'aac' };\n    const audioFile = await extractAudioFromVideo(videoFile, options);\n    expect(audioFile).toBeInstanceOf(File);\n    expect(audioFile.name.endsWith('.aac')).toBe(true);\n    expect(audioFile.type).toBe('audio/aac');\n  });\n\n  it('should extract audio as MP3', async () => {\n    const options: InitialValuesType = { outputFormat: 'mp3' };\n    const audioFile = await extractAudioFromVideo(videoFile, options);\n    expect(audioFile).toBeInstanceOf(File);\n    expect(audioFile.name.endsWith('.mp3')).toBe(true);\n    expect(audioFile.type).toBe('audio/mp3');\n  });\n\n  it('should extract audio as WAV', async () => {\n    const options: InitialValuesType = { outputFormat: 'wav' };\n    const audioFile = await extractAudioFromVideo(videoFile, options);\n    expect(audioFile).toBeInstanceOf(File);\n    expect(audioFile.name.endsWith('.wav')).toBe(true);\n    expect(audioFile.type).toBe('audio/wav');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/audio/extract-audio/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { extractAudioFromVideo } from './service';\nimport { InitialValuesType } from './types';\nimport ToolVideoInput from '@components/input/ToolVideoInput';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  outputFormat: 'aac'\n};\n\nexport default function ExtractAudio({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('audio');\n  const [file, setFile] = useState<File | null>(null);\n  const [audioFile, setAudioFile] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => {\n    return [\n      {\n        title: t('extractAudio.outputFormat'),\n        component: (\n          <Box>\n            <SelectWithDesc\n              selected={values.outputFormat}\n              onChange={(value) => {\n                updateField('outputFormat', value.toString());\n              }}\n              options={[\n                { label: 'AAC', value: 'aac' },\n                { label: 'MP3', value: 'mp3' },\n                { label: 'WAV', value: 'wav' }\n              ]}\n              description={t('extractAudio.outputFormatDescription')}\n            />\n          </Box>\n        )\n      }\n    ];\n  };\n\n  const compute = async (values: InitialValuesType, input: File | null) => {\n    if (!input) return;\n    try {\n      setLoading(true);\n      const audioFileObj = await extractAudioFromVideo(input, values);\n      setAudioFile(audioFileObj);\n    } catch (err) {\n      console.error(err);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={file}\n      inputComponent={\n        <ToolVideoInput\n          value={file}\n          onChange={setFile}\n          title={t('extractAudio.inputTitle')}\n        />\n      }\n      resultComponent={\n        loading ? (\n          <ToolFileResult\n            title={t('extractAudio.extractingAudio')}\n            value={null}\n            loading={true}\n          />\n        ) : (\n          <ToolFileResult\n            title={t('extractAudio.resultTitle')}\n            value={audioFile}\n          />\n        )\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      toolInfo={{\n        title: t('extractAudio.toolInfo.title', { title }),\n        description: longDescription\n      }}\n      setInput={setFile}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/audio/extract-audio/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('audio', {\n  path: 'extract-audio',\n  icon: 'mdi:music-note',\n\n  keywords: [\n    'extract',\n    'audio',\n    'video',\n    'mp3',\n    'aac',\n    'wav',\n    'audio extraction',\n    'media',\n    'convert'\n  ],\n\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'audio:extractAudio.title',\n    description: 'audio:extractAudio.description',\n    shortDescription: 'audio:extractAudio.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/audio/extract-audio/service.ts",
    "content": "import { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\nimport { InitialValuesType } from './types';\n\nconst ffmpeg = new FFmpeg();\n\nexport async function extractAudioFromVideo(\n  input: File,\n  options: InitialValuesType\n): Promise<File> {\n  if (!ffmpeg.loaded) {\n    await ffmpeg.load({\n      wasmURL:\n        'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n    });\n  }\n\n  const inputName = 'input.mp4';\n  await ffmpeg.writeFile(inputName, await fetchFile(input));\n\n  const configuredOutputAudioFormat = options.outputFormat;\n  const outputName = `output.${configuredOutputAudioFormat}`;\n  const args: string[] = ['-i', inputName, '-vn'];\n\n  if (configuredOutputAudioFormat === 'mp3') {\n    args.push(\n      '-ar',\n      '44100',\n      '-ac',\n      '2',\n      '-b:a',\n      '192k',\n      '-f',\n      'mp3',\n      outputName\n    );\n  } else if (configuredOutputAudioFormat === 'wav') {\n    args.push(\n      '-acodec',\n      'pcm_s16le',\n      '-ar',\n      '44100',\n      '-ac',\n      '2',\n      '-f',\n      'wav',\n      outputName\n    );\n  } else {\n    // Default to AAC or copy\n    args.push('-acodec', 'copy', outputName);\n  }\n\n  await ffmpeg.exec(args);\n\n  const extractedAudio = await ffmpeg.readFile(outputName);\n\n  return new File(\n    [\n      new Blob([extractedAudio as any], {\n        type: `audio/${configuredOutputAudioFormat}`\n      })\n    ],\n    `${input.name.replace(\n      /\\.[^/.]+$/,\n      ''\n    )}_audio.${configuredOutputAudioFormat}`,\n    { type: `audio/${configuredOutputAudioFormat}` }\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/audio/extract-audio/types.ts",
    "content": "export type InitialValuesType = {\n  outputFormat: string;\n};\n"
  },
  {
    "path": "src/pages/tools/audio/index.ts",
    "content": "import { tool as audioMergeAudio } from './merge-audio/meta';\nimport { tool as audioTrim } from './trim/meta';\nimport { tool as audioChangeSpeed } from './change-speed/meta';\nimport { tool as audioExtractAudio } from './extract-audio/meta';\n\nexport const audioTools = [\n  audioExtractAudio,\n  audioChangeSpeed,\n  audioTrim,\n  audioMergeAudio\n];\n"
  },
  {
    "path": "src/pages/tools/audio/merge-audio/index.tsx",
    "content": "import { Box, FormControlLabel, Radio, RadioGroup } from '@mui/material';\nimport React, { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { InitialValuesType } from './types';\nimport ToolMultipleAudioInput, {\n  MultiAudioInput\n} from '@components/input/ToolMultipleAudioInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport { mergeAudioFiles } from './service';\n\nconst initialValues: InitialValuesType = {\n  outputFormat: 'mp3'\n};\n\nconst formatOptions = [\n  { label: 'MP3', value: 'mp3' },\n  { label: 'AAC', value: 'aac' },\n  { label: 'WAV', value: 'wav' }\n];\n\nexport default function MergeAudio({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('audio');\n  const [input, setInput] = useState<MultiAudioInput[]>([]);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const compute = async (\n    optionsValues: InitialValuesType,\n    input: MultiAudioInput[]\n  ) => {\n    if (input.length === 0) return;\n    setLoading(true);\n    try {\n      const files = input.map((item) => item.file);\n      const mergedFile = await mergeAudioFiles(files, optionsValues);\n      setResult(mergedFile);\n    } catch (err) {\n      console.error(`Failed to merge audio: ${err}`);\n      setResult(null);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('mergeAudio.outputFormat'),\n      component: (\n        <Box mt={2}>\n          <RadioGroup\n            row\n            value={values.outputFormat}\n            onChange={(e) =>\n              updateField(\n                'outputFormat',\n                e.target.value as 'mp3' | 'aac' | 'wav'\n              )\n            }\n          >\n            {formatOptions.map((opt) => (\n              <FormControlLabel\n                key={opt.value}\n                value={opt.value}\n                control={<Radio />}\n                label={opt.label}\n              />\n            ))}\n          </RadioGroup>\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolMultipleAudioInput\n          value={input}\n          onChange={setInput}\n          accept={['audio/*', '.mp3', '.wav', '.aac']}\n          title={t('mergeAudio.inputTitle')}\n          type=\"audio\"\n        />\n      }\n      resultComponent={\n        loading ? (\n          <ToolFileResult\n            title={t('mergeAudio.mergingAudio')}\n            value={null}\n            loading={true}\n          />\n        ) : (\n          <ToolFileResult\n            title={t('mergeAudio.resultTitle')}\n            value={result}\n            extension={result ? result.name.split('.').pop() : undefined}\n          />\n        )\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('mergeAudio.toolInfo.title', { title }),\n        description: longDescription\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/audio/merge-audio/merge-audio.service.test.ts",
    "content": "import { expect, describe, it, vi } from 'vitest';\n\n// Mock FFmpeg since it doesn't support Node.js\nvi.mock('@ffmpeg/ffmpeg', () => ({\n  FFmpeg: vi.fn().mockImplementation(() => ({\n    loaded: false,\n    load: vi.fn().mockResolvedValue(undefined),\n    writeFile: vi.fn().mockResolvedValue(undefined),\n    exec: vi.fn().mockResolvedValue(undefined),\n    readFile: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4, 5])),\n    deleteFile: vi.fn().mockResolvedValue(undefined)\n  }))\n}));\n\nvi.mock('@ffmpeg/util', () => ({\n  fetchFile: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4, 5]))\n}));\n\nimport { mergeAudioFiles } from './service';\n\ndescribe('mergeAudioFiles', () => {\n  it('should merge multiple audio files', async () => {\n    // Create mock audio files\n    const mockAudioData1 = new Uint8Array([0, 1, 2, 3, 4, 5]);\n    const mockAudioData2 = new Uint8Array([6, 7, 8, 9, 10, 11]);\n\n    const mockFile1 = new File([mockAudioData1], 'test1.mp3', {\n      type: 'audio/mp3'\n    });\n    const mockFile2 = new File([mockAudioData2], 'test2.mp3', {\n      type: 'audio/mp3'\n    });\n\n    const options = {\n      outputFormat: 'mp3' as const\n    };\n\n    const result = await mergeAudioFiles([mockFile1, mockFile2], options);\n    expect(result).toBeInstanceOf(File);\n    expect(result.name).toBe('merged_audio.mp3');\n    expect(result.type).toBe('audio/mp3');\n  });\n\n  it('should handle different output formats', async () => {\n    const mockAudioData = new Uint8Array([0, 1, 2, 3, 4, 5]);\n    const mockFile = new File([mockAudioData], 'test.wav', {\n      type: 'audio/wav'\n    });\n\n    const options = {\n      outputFormat: 'aac' as const\n    };\n\n    const result = await mergeAudioFiles([mockFile], options);\n    expect(result).toBeInstanceOf(File);\n    expect(result.name).toBe('merged_audio.aac');\n    expect(result.type).toBe('audio/aac');\n  });\n\n  it('should throw error when no input files provided', async () => {\n    const options = {\n      outputFormat: 'mp3' as const\n    };\n\n    try {\n      await mergeAudioFiles([], options);\n      expect.fail('Should have thrown an error');\n    } catch (error) {\n      expect(error).toBeInstanceOf(Error);\n      expect((error as Error).message).toBe('No input files provided');\n    }\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/audio/merge-audio/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('audio', {\n  i18n: {\n    name: 'audio:mergeAudio.title',\n    description: 'audio:mergeAudio.description',\n    shortDescription: 'audio:mergeAudio.shortDescription',\n    longDescription: 'audio:mergeAudio.longDescription',\n    userTypes: ['generalUsers', 'developers']\n  },\n\n  path: 'merge-audio',\n  icon: 'fluent:merge-20-regular',\n\n  keywords: [\n    'merge',\n    'audio',\n    'combine',\n    'concatenate',\n    'join',\n    'mp3',\n    'aac',\n    'wav',\n    'audio editing',\n    'multiple files'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/audio/merge-audio/service.ts",
    "content": "import { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\nimport { InitialValuesType } from './types';\n\nconst ffmpeg = new FFmpeg();\n\nexport async function mergeAudioFiles(\n  inputs: File[],\n  options: InitialValuesType\n): Promise<File> {\n  if (!ffmpeg.loaded) {\n    await ffmpeg.load({\n      wasmURL:\n        'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n    });\n  }\n\n  if (inputs.length === 0) {\n    throw new Error('No input files provided');\n  }\n\n  const { outputFormat } = options;\n  const outputName = `output.${outputFormat}`;\n\n  // 1. Convert all inputs to WAV\n  const tempWavNames: string[] = [];\n  for (let i = 0; i < inputs.length; i++) {\n    const inputName = `input${i}`;\n    const tempWavName = `temp${i}.wav`;\n    await ffmpeg.writeFile(inputName, await fetchFile(inputs[i]));\n    await ffmpeg.exec([\n      '-i',\n      inputName,\n      '-acodec',\n      'pcm_s16le',\n      '-ar',\n      '44100',\n      '-ac',\n      '2',\n      tempWavName\n    ]);\n    tempWavNames.push(tempWavName);\n    await ffmpeg.deleteFile(inputName);\n  }\n\n  // 2. Create file list for concat\n  const fileListName = 'filelist.txt';\n  const fileListContent = tempWavNames\n    .map((name) => `file '${name}'`)\n    .join('\\n');\n  await ffmpeg.writeFile(fileListName, fileListContent);\n\n  // 3. Concatenate WAV files\n  const concatWav = 'concat.wav';\n  await ffmpeg.exec([\n    '-f',\n    'concat',\n    '-safe',\n    '0',\n    '-i',\n    fileListName,\n    '-c',\n    'copy',\n    concatWav\n  ]);\n\n  // 4. Convert concatenated WAV to requested output format\n  let finalOutput = concatWav;\n  if (outputFormat !== 'wav') {\n    const args = ['-i', concatWav];\n    if (outputFormat === 'mp3') {\n      args.push(\n        '-ar',\n        '44100',\n        '-ac',\n        '2',\n        '-b:a',\n        '192k',\n        '-f',\n        'mp3',\n        outputName\n      );\n    } else if (outputFormat === 'aac') {\n      args.push('-c:a', 'aac', '-b:a', '192k', '-f', 'adts', outputName);\n    }\n    await ffmpeg.exec(args);\n    finalOutput = outputName;\n  }\n\n  const mergedAudio = await ffmpeg.readFile(finalOutput);\n\n  let mimeType = 'audio/wav';\n  if (outputFormat === 'mp3') mimeType = 'audio/mp3';\n  if (outputFormat === 'aac') mimeType = 'audio/aac';\n\n  // Clean up files\n  for (const tempWavName of tempWavNames) {\n    await ffmpeg.deleteFile(tempWavName);\n  }\n  await ffmpeg.deleteFile(fileListName);\n  await ffmpeg.deleteFile(concatWav);\n  if (outputFormat !== 'wav') {\n    await ffmpeg.deleteFile(outputName);\n  }\n\n  return new File(\n    [\n      new Blob([mergedAudio as any], {\n        type: mimeType\n      })\n    ],\n    `merged_audio.${outputFormat}`,\n    { type: mimeType }\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/audio/merge-audio/types.ts",
    "content": "export type InitialValuesType = {\n  outputFormat: 'mp3' | 'aac' | 'wav';\n};\n"
  },
  {
    "path": "src/pages/tools/audio/trim/index.tsx",
    "content": "import { Box, FormControlLabel, Radio, RadioGroup } from '@mui/material';\nimport React, { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { InitialValuesType } from './types';\nimport ToolAudioInput from '@components/input/ToolAudioInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { trimAudio } from './service';\n\nconst initialValues: InitialValuesType = {\n  startTime: '00:00:00',\n  endTime: '00:01:00',\n  outputFormat: 'mp3'\n};\n\nconst formatOptions = [\n  { label: 'MP3', value: 'mp3' },\n  { label: 'AAC', value: 'aac' },\n  { label: 'WAV', value: 'wav' }\n];\n\nexport default function Trim({ title, longDescription }: ToolComponentProps) {\n  const { t } = useTranslation('audio');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const compute = async (\n    optionsValues: InitialValuesType,\n    input: File | null\n  ) => {\n    if (!input) return;\n    setLoading(true);\n    try {\n      const trimmedFile = await trimAudio(input, optionsValues);\n      setResult(trimmedFile);\n    } catch (err) {\n      console.error(`Failed to trim audio: ${err}`);\n      setResult(null);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('trim.timeSettings'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.startTime}\n            onOwnChange={(val) => updateField('startTime', val)}\n            description={t('trim.startTimeDescription')}\n            label={t('trim.startTime')}\n          />\n          <Box mt={2}>\n            <TextFieldWithDesc\n              value={values.endTime}\n              onOwnChange={(val) => updateField('endTime', val)}\n              description={t('trim.endTimeDescription')}\n              label={t('trim.endTime')}\n            />\n          </Box>\n        </Box>\n      )\n    },\n    {\n      title: t('trim.outputFormat'),\n      component: (\n        <Box mt={2}>\n          <RadioGroup\n            row\n            value={values.outputFormat}\n            onChange={(e) =>\n              updateField(\n                'outputFormat',\n                e.target.value as 'mp3' | 'aac' | 'wav'\n              )\n            }\n          >\n            {formatOptions.map((opt) => (\n              <FormControlLabel\n                key={opt.value}\n                value={opt.value}\n                control={<Radio />}\n                label={opt.label}\n              />\n            ))}\n          </RadioGroup>\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolAudioInput\n          value={input}\n          onChange={setInput}\n          title={t('trim.inputTitle')}\n        />\n      }\n      resultComponent={\n        loading ? (\n          <ToolFileResult\n            title={t('trim.trimmingAudio')}\n            value={null}\n            loading={true}\n          />\n        ) : (\n          <ToolFileResult\n            title={t('trim.resultTitle')}\n            value={result}\n            extension={result ? result.name.split('.').pop() : undefined}\n          />\n        )\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('trim.toolInfo.title', { title }),\n        description: longDescription\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/audio/trim/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('audio', {\n  i18n: {\n    name: 'audio:trim.title',\n    description: 'audio:trim.description',\n    shortDescription: 'audio:trim.shortDescription',\n    longDescription: 'audio:trim.longDescription',\n    userTypes: ['generalUsers', 'developers']\n  },\n\n  path: 'trim',\n  icon: 'mdi:scissors-cutting',\n\n  keywords: [\n    'trim',\n    'audio',\n    'cut',\n    'segment',\n    'extract',\n    'mp3',\n    'aac',\n    'wav',\n    'audio editing',\n    'time'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/audio/trim/service.ts",
    "content": "import { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\nimport { InitialValuesType } from './types';\n\nconst ffmpeg = new FFmpeg();\n\nexport async function trimAudio(\n  input: File,\n  options: InitialValuesType\n): Promise<File> {\n  if (!ffmpeg.loaded) {\n    await ffmpeg.load({\n      wasmURL:\n        'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n    });\n  }\n\n  const inputName = 'input.mp3';\n  await ffmpeg.writeFile(inputName, await fetchFile(input));\n\n  const { startTime, endTime, outputFormat } = options;\n  const outputName = `output.${outputFormat}`;\n\n  // Build FFmpeg arguments for trimming\n  let args: string[] = [\n    '-i',\n    inputName,\n    '-ss',\n    startTime, // Start time\n    '-to',\n    endTime, // End time\n    '-c',\n    'copy' // Copy without re-encoding for speed\n  ];\n\n  // Add format-specific arguments\n  if (outputFormat === 'mp3') {\n    args = [\n      '-i',\n      inputName,\n      '-ss',\n      startTime,\n      '-to',\n      endTime,\n      '-ar',\n      '44100',\n      '-ac',\n      '2',\n      '-b:a',\n      '192k',\n      '-f',\n      'mp3',\n      outputName\n    ];\n  } else if (outputFormat === 'aac') {\n    args = [\n      '-i',\n      inputName,\n      '-ss',\n      startTime,\n      '-to',\n      endTime,\n      '-c:a',\n      'aac',\n      '-b:a',\n      '192k',\n      '-f',\n      'adts',\n      outputName\n    ];\n  } else if (outputFormat === 'wav') {\n    args = [\n      '-i',\n      inputName,\n      '-ss',\n      startTime,\n      '-to',\n      endTime,\n      '-acodec',\n      'pcm_s16le',\n      '-ar',\n      '44100',\n      '-ac',\n      '2',\n      '-f',\n      'wav',\n      outputName\n    ];\n  }\n\n  await ffmpeg.exec(args);\n\n  const trimmedAudio = await ffmpeg.readFile(outputName);\n\n  let mimeType = 'audio/mp3';\n  if (outputFormat === 'aac') mimeType = 'audio/aac';\n  if (outputFormat === 'wav') mimeType = 'audio/wav';\n\n  return new File(\n    [\n      new Blob([trimmedAudio as any], {\n        type: mimeType\n      })\n    ],\n    `${input.name.replace(/\\.[^/.]+$/, '')}_trimmed.${outputFormat}`,\n    { type: mimeType }\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/audio/trim/trim.service.test.ts",
    "content": "import { expect, describe, it, vi } from 'vitest';\n\n// Mock FFmpeg since it doesn't support Node.js\nvi.mock('@ffmpeg/ffmpeg', () => ({\n  FFmpeg: vi.fn().mockImplementation(() => ({\n    loaded: false,\n    load: vi.fn().mockResolvedValue(undefined),\n    writeFile: vi.fn().mockResolvedValue(undefined),\n    exec: vi.fn().mockResolvedValue(undefined),\n    readFile: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4, 5])),\n    deleteFile: vi.fn().mockResolvedValue(undefined)\n  }))\n}));\n\nvi.mock('@ffmpeg/util', () => ({\n  fetchFile: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4, 5]))\n}));\n\nimport { trimAudio } from './service';\n\ndescribe('trimAudio', () => {\n  it('should trim audio file with valid time parameters', async () => {\n    // Create a mock audio file\n    const mockAudioData = new Uint8Array([0, 1, 2, 3, 4, 5]);\n    const mockFile = new File([mockAudioData], 'test.mp3', {\n      type: 'audio/mp3'\n    });\n\n    const options = {\n      startTime: '00:00:10',\n      endTime: '00:00:20',\n      outputFormat: 'mp3' as const\n    };\n\n    const result = await trimAudio(mockFile, options);\n    expect(result).toBeInstanceOf(File);\n    expect(result.name).toContain('_trimmed.mp3');\n    expect(result.type).toBe('audio/mp3');\n  });\n\n  it('should handle different output formats', async () => {\n    const mockAudioData = new Uint8Array([0, 1, 2, 3, 4, 5]);\n    const mockFile = new File([mockAudioData], 'test.wav', {\n      type: 'audio/wav'\n    });\n\n    const options = {\n      startTime: '00:00:00',\n      endTime: '00:00:30',\n      outputFormat: 'wav' as const\n    };\n\n    const result = await trimAudio(mockFile, options);\n    expect(result).toBeInstanceOf(File);\n    expect(result.name).toContain('_trimmed.wav');\n    expect(result.type).toBe('audio/wav');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/audio/trim/types.ts",
    "content": "export type InitialValuesType = {\n  startTime: string;\n  endTime: string;\n  outputFormat: 'mp3' | 'aac' | 'wav';\n};\n"
  },
  {
    "path": "src/pages/tools/converters/audio-converter/audio-converter.service.test.ts",
    "content": "import { expect, describe, it, vi, beforeEach } from 'vitest';\nimport { convertAudio } from './service';\nimport { InitialValuesType } from './types';\n\n// Mock FFmpeg since it doesn't support Node.js in tests\nvi.mock('@ffmpeg/ffmpeg', () => ({\n  FFmpeg: vi.fn().mockImplementation(() => ({\n    loaded: false,\n    load: vi.fn().mockResolvedValue(undefined),\n    writeFile: vi.fn().mockResolvedValue(undefined),\n    exec: vi.fn().mockResolvedValue(undefined),\n    readFile: vi.fn().mockResolvedValue(new Uint8Array([10, 20, 30, 40, 50])),\n    deleteFile: vi.fn().mockResolvedValue(undefined)\n  }))\n}));\n\nvi.mock('@ffmpeg/util', () => ({\n  fetchFile: vi.fn().mockResolvedValue(new Uint8Array([10, 20, 30, 40, 50]))\n}));\n\ndescribe('convertAudio', () => {\n  let mockInputFile: File;\n\n  beforeEach(() => {\n    const mockAudioData = new Uint8Array([1, 2, 3, 4, 5]);\n    mockInputFile = new File([mockAudioData], 'input.aac', {\n      type: 'audio/aac'\n    });\n  });\n\n  it('should convert to MP3 format correctly', async () => {\n    const options: InitialValuesType = { outputFormat: 'mp3' };\n    const result = await convertAudio(mockInputFile, options);\n\n    expect(result).toBeInstanceOf(File);\n    expect(result.name).toBe('input.mp3');\n    expect(result.type).toBe('audio/mpeg');\n  });\n\n  it('should convert to AAC format correctly', async () => {\n    const options: InitialValuesType = { outputFormat: 'aac' };\n    const result = await convertAudio(mockInputFile, options);\n\n    expect(result).toBeInstanceOf(File);\n    expect(result.name).toBe('input.aac');\n    expect(result.type).toBe('audio/aac');\n  });\n\n  it('should convert to WAV format correctly', async () => {\n    const options: InitialValuesType = { outputFormat: 'wav' };\n    const result = await convertAudio(mockInputFile, options);\n\n    expect(result).toBeInstanceOf(File);\n    expect(result.name).toBe('input.wav');\n    expect(result.type).toBe('audio/wav');\n  });\n\n  it('should convert to FLAC format correctly', async () => {\n    const options: InitialValuesType = { outputFormat: 'flac' };\n    const result = await convertAudio(mockInputFile, options);\n\n    expect(result).toBeInstanceOf(File);\n    expect(result.name).toBe('input.flac');\n    expect(result.type).toBe('audio/flac');\n  });\n\n  it('should convert to OGG format correctly', async () => {\n    const options: InitialValuesType = { outputFormat: 'ogg' };\n    const result = await convertAudio(mockInputFile, options);\n\n    expect(result).toBeInstanceOf(File);\n    expect(result.name).toBe('input.ogg');\n    expect(result.type).toBe('audio/ogg');\n  });\n\n  it('should return original file if input format matches output format', async () => {\n    const options: InitialValuesType = { outputFormat: 'aac' };\n    const result = await convertAudio(mockInputFile, options);\n\n    expect(result).toBe(mockInputFile); // Same instance\n    expect(result.name).toBe('input.aac');\n  });\n\n  it('should handle files without extensions', async () => {\n    const fileNoExt = new File([new Uint8Array([1, 2, 3])], 'audiofile', {\n      type: 'audio/aac'\n    });\n    const options: InitialValuesType = { outputFormat: 'mp3' };\n    const result = await convertAudio(fileNoExt, options);\n\n    expect(result).toBeInstanceOf(File);\n    expect(result.name).toBe('audiofile.mp3');\n    expect(result.type).toBe('audio/mpeg');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/converters/audio-converter/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Box } from '@mui/material';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolAudioInput from '@components/input/ToolAudioInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport { convertAudio } from './service';\nimport { useTranslation } from 'react-i18next';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { InitialValuesType, AUDIO_FORMATS, AudioFormat } from './types';\n\nconst initialValues: InitialValuesType = {\n  outputFormat: 'mp3'\n};\n\nexport default function AudioConverter({ title }: ToolComponentProps) {\n  const { t } = useTranslation('converters');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const compute = async (\n    values: InitialValuesType,\n    inputFile: File | null\n  ): Promise<void> => {\n    if (!inputFile) return;\n\n    try {\n      setLoading(true);\n      const resultFile = await convertAudio(inputFile, values);\n      setResult(resultFile);\n    } catch (error) {\n      console.error('Conversion failed:', error);\n      setResult(null);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  // Explicitly type getGroups to match GetGroupsType<InitialValuesType>\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('audioConverter.outputFormat'),\n      component: (\n        <Box>\n          <SelectWithDesc\n            selected={values.outputFormat}\n            onChange={(value) => updateField('outputFormat', value)}\n            options={Object.entries(AUDIO_FORMATS).map(([value]) => ({\n              label: value.toUpperCase(),\n              value: value as AudioFormat\n            }))}\n            description={t('audioConverter.outputFormatDescription')}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolAudioInput\n          value={input}\n          onChange={setInput}\n          title={t('audioConverter.inputTitle')}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          value={result}\n          title={t('audioConverter.outputTitle')}\n          loading={loading}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('audioConverter.title'),\n        description: t('audioConverter.longDescription')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/converters/audio-converter/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('converters', {\n  i18n: {\n    name: 'converters:audioConverter.title',\n    description: 'converters:audioConverter.description',\n    shortDescription: 'converters:audioConverter.shortDescription',\n    longDescription: 'converters:audioConverter.longDescription'\n  },\n  path: 'audio-converter',\n  icon: 'mdi:music-note-outline',\n  keywords: ['audio', 'converter'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/converters/audio-converter/service.ts",
    "content": "import { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\nimport { InitialValuesType, AUDIO_FORMATS } from './types';\nimport { getFileExtension } from 'utils/file';\n\n/**\n * optimzed call for FFmpeg instance creation,\n * avoiding to download required FFmpeg binaries on every reload\n */\nconst ffmpeg = new FFmpeg();\nlet isLoaded = false;\n\nasync function loadFFmpeg() {\n  if (!isLoaded) {\n    await ffmpeg.load();\n    isLoaded = true;\n  }\n}\n\n/**\n * Converts input audio file to selected output format ('mp3', 'aac', or 'wav').\n * Supports any input audio file type accepted by FFmpeg.\n *\n * @param input - Source audio File\n * @param outputFormat - 'mp3' | 'aac' | 'wav'\n * @returns Converted audio File\n */\nexport async function convertAudio(\n  input: File,\n  options: InitialValuesType\n): Promise<File> {\n  await loadFFmpeg();\n\n  // Use the original input extension for input filename\n  const inputExt = getFileExtension(input.name);\n\n  if (inputExt === options.outputFormat) return input;\n\n  const inputFileName = inputExt ? `input.${inputExt}` : 'input';\n  const outputFileName = `output.${options.outputFormat}`;\n\n  // Write the input file to FFmpeg FS\n  await ffmpeg.writeFile(inputFileName, await fetchFile(input));\n\n  // Build the FFmpeg args depending on the output format\n  // You can customize the codec and bitrate options per format here\n\n  const format = AUDIO_FORMATS[options.outputFormat];\n  const { codec, bitrate, mimeType } = format;\n\n  const args = bitrate\n    ? ['-i', inputFileName, '-c:a', codec, '-b:a', bitrate, outputFileName]\n    : ['-i', inputFileName, '-c:a', codec, outputFileName];\n\n  // Execute ffmpeg with arguments\n  try {\n    await ffmpeg.exec(args);\n\n    // Read the output file from FFmpeg FS\n    const data = await ffmpeg.readFile(outputFileName);\n\n    // Create a new File with the original name but new extension\n    const baseName = input.name.replace(/\\.[^.]+$/, '');\n    const convertedFileName = `${baseName}.${options.outputFormat}`;\n\n    return new File(\n      [new Blob([data as any], { type: mimeType })],\n      convertedFileName,\n      {\n        type: mimeType\n      }\n    );\n  } finally {\n    // Clean up FFmpeg virtual filesystem\n    try {\n      await ffmpeg.deleteFile(inputFileName);\n      await ffmpeg.deleteFile(outputFileName);\n    } catch (e) {\n      // Ignore cleanup errors\n    }\n  }\n}\n"
  },
  {
    "path": "src/pages/tools/converters/audio-converter/types.ts",
    "content": "export const AUDIO_FORMATS = {\n  // Lossy formats\n  mp3: { codec: 'libmp3lame', bitrate: '192k', mimeType: 'audio/mpeg' },\n  aac: { codec: 'aac', bitrate: '192k', mimeType: 'audio/aac' },\n  ogg: { codec: 'libvorbis', bitrate: '192k', mimeType: 'audio/ogg' },\n\n  // Lossless formats\n  wav: { codec: 'pcm_s16le', bitrate: null, mimeType: 'audio/wav' },\n  flac: { codec: 'flac', bitrate: null, mimeType: 'audio/flac' }\n} as const;\n\nexport type AudioFormat = keyof typeof AUDIO_FORMATS;\n\nexport type InitialValuesType = {\n  outputFormat: AudioFormat;\n};\n"
  },
  {
    "path": "src/pages/tools/converters/index.ts",
    "content": "import { tool as convertersAudioConverter } from './audio-converter/meta';\n\nexport const convertersTools = [convertersAudioConverter];\n"
  },
  {
    "path": "src/pages/tools/csv/change-csv-separator/change-csv-separator.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { changeCsvSeparator } from './service';\nimport { InitialValuesType } from './types';\n\ndescribe('changeCsvSeparator', () => {\n  it('should change the separator from comma to semicolon', () => {\n    const inputCsv = 'name,age,city\\nJohn,30,New York';\n    const options: InitialValuesType = {\n      inputSeparator: ',',\n      inputQuoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: false,\n      outputSeparator: ';',\n      outputQuoteAll: false,\n      OutputQuoteCharacter: '\"'\n    };\n    const result = changeCsvSeparator(inputCsv, options);\n    expect(result).toBe('name;age;city\\nJohn;30;New York');\n  });\n\n  it('should handle empty input gracefully', () => {\n    const inputCsv = '';\n    const options: InitialValuesType = {\n      inputSeparator: ',',\n      inputQuoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: false,\n      outputSeparator: ';',\n      outputQuoteAll: false,\n      OutputQuoteCharacter: '\"'\n    };\n    const result = changeCsvSeparator(inputCsv, options);\n    expect(result).toBe('');\n  });\n\n  it('should not modify the CSV if the separator is already correct', () => {\n    const inputCsv = 'name;age;city\\nJohn;30;New York';\n    const options: InitialValuesType = {\n      inputSeparator: ';',\n      inputQuoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: false,\n      outputSeparator: ';',\n      outputQuoteAll: false,\n      OutputQuoteCharacter: '\"'\n    };\n    const result = changeCsvSeparator(inputCsv, options);\n    expect(result).toBe(inputCsv);\n  });\n\n  it('should handle custom separators', () => {\n    const inputCsv = 'name|age|city\\nJohn|30|New York';\n    const options: InitialValuesType = {\n      inputSeparator: '|',\n      inputQuoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: false,\n      outputSeparator: ';',\n      outputQuoteAll: false,\n      OutputQuoteCharacter: '\"'\n    };\n    const result = changeCsvSeparator(inputCsv, options);\n    expect(result).toBe('name;age;city\\nJohn;30;New York');\n  });\n\n  it('should quote all output values', () => {\n    const inputCsv = 'name|age|city\\nJohn|30|New York';\n    const options: InitialValuesType = {\n      inputSeparator: '|',\n      inputQuoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: false,\n      outputSeparator: ';',\n      outputQuoteAll: true,\n      OutputQuoteCharacter: '\"'\n    };\n    const result = changeCsvSeparator(inputCsv, options);\n    expect(result).toBe('\"name\";\"age\";\"city\"\\n\"John\";\"30\";\"New York\"');\n  });\n\n  it('should remove quotes from input values', () => {\n    const inputCsv = '\"name\"|\"age\"|\"city\"\\n\"John\"|\"30\"|\"New York\"';\n    const options: InitialValuesType = {\n      inputSeparator: '|',\n      inputQuoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: false,\n      outputSeparator: ';',\n      outputQuoteAll: false,\n      OutputQuoteCharacter: '\"'\n    };\n    const result = changeCsvSeparator(inputCsv, options);\n    expect(result).toBe('name;age;city\\nJohn;30;New York');\n  });\n\n  it('should handle emptylines', () => {\n    const inputCsv = '\"name\"|\"age\"|\"city\"\\n\\n\"John\"|\"30\"|\"New York\"';\n    const options: InitialValuesType = {\n      inputSeparator: '|',\n      inputQuoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: true,\n      outputSeparator: ';',\n      outputQuoteAll: false,\n      OutputQuoteCharacter: '\"'\n    };\n    const result = changeCsvSeparator(inputCsv, options);\n    expect(result).toBe('name;age;city\\nJohn;30;New York');\n  });\n\n  it('should handle emptylines', () => {\n    const inputCsv = '\"name\"|\"age\"|\"city\"\\n\\n\"John\"|\"30\"|\"New York\"';\n    const options: InitialValuesType = {\n      inputSeparator: '|',\n      inputQuoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: true,\n      outputSeparator: ';',\n      outputQuoteAll: false,\n      OutputQuoteCharacter: '\"'\n    };\n    const result = changeCsvSeparator(inputCsv, options);\n    expect(result).toBe('name;age;city\\nJohn;30;New York');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/csv/change-csv-separator/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { changeCsvSeparator } from './service';\nimport { InitialValuesType } from './types';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\n\nconst initialValues: InitialValuesType = {\n  inputSeparator: ',',\n  inputQuoteCharacter: '\"',\n  commentCharacter: '#',\n  emptyLines: false,\n  outputSeparator: ';',\n  outputQuoteAll: false,\n  OutputQuoteCharacter: '\"'\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Change the CSV Delimiter to a Semicolon',\n    description:\n      'In this example, we change the column separator to the semicolon separator in a CSV file containing data about countries, their populations, and population densities. As you can see, the input CSV file uses the standard commas as separators. After specifying this delimiter in the source CSV options, we set a new CSV delimiter for the output file to a semicolon, resulting in a new CSV file that now uses semicolons \";\" in the output. Such CSV files with semicolons are called SSV files (semicolon-separated values files)',\n    sampleText: `country,population,density\nChina,1412,152\nIndia,1408,428\nUnited States,331,37\nIndonesia,273,145\nPakistan,231,232\nBrazil,214,26`,\n    sampleResult: `country;population;density\nChina;1412;152\nIndia;1408;428\nUnited States;331;37\nIndonesia;273;145\nPakistan;231;232\nBrazil;214;26`,\n    sampleOptions: {\n      inputSeparator: ',',\n      inputQuoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: false,\n      outputSeparator: ';',\n      outputQuoteAll: false,\n      OutputQuoteCharacter: '\"'\n    }\n  },\n  {\n    title: 'Restore a CSV File to the Standard Format',\n    description:\n      'In this example, a data scientist working with flowers was given an unusual CSV file that uses the vertical bar symbol as the field separator (such files are called PSV files – pipe-separated values files). To transform the file back to the standard comma-separated values (CSV) file, in the options, she set the input delimiter to \"|\" and the new delimiter to \",\". She also wrapped the output fields in single quotes, enabled the option to remove empty lines from the input, and discarded comment lines starting with the \"#\" symbol.',\n    sampleText: `species|height|days|temperature\n\nSunflower|50cm|30|25°C\nRose|40cm|25|22°C\nTulip|35cm|20|18°C\nDaffodil|30cm|15|20°C\n\nLily|45cm|28|23°C\n#pumpkin\nBrazil,214,26`,\n    sampleResult: `'species','height','days','temperature'\n'Sunflower','50cm','30','25°C'\n'Rose','40cm','25','22°C'\n'Tulip','35cm','20','18°C'\n'Daffodil','30cm','15','20°C'\n'Lily','45cm','28','23°C'`,\n    sampleOptions: {\n      inputSeparator: '|',\n      inputQuoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: true,\n      outputSeparator: ',',\n      outputQuoteAll: true,\n      OutputQuoteCharacter: \"'\"\n    }\n  },\n  {\n    title: 'Plants vs. Zombies CSV',\n    description:\n      'In this example, we import CSV data with zombie characters from the game Plants vs. Zombies. The data includes zombies names, the level at which they first appear in the game, their health, damage, and speed. The data follows the standard CSV format, with commas serving as field separators. To change the readability of the file, we replace the usual comma delimiter with a slash symbol, creating a slash-separated values file.',\n    sampleText: `zombie_name,first_seen,health,damage,speed\nNormal Zombie,Level 1-1,181,100,4.7\nConehead Zombie,Level 1-3,551,100,4.7\nBuckethead Zombi,Level 1-8,1281,100,4.7\nNewspaper Zombie,Level 2-1,331,100,4.7\nFootball Zombie,Level 2-6,1581,100,2.5\nDancing Zombie,Level 2-8,335,100,1.5\nZomboni,Level 3-6,1151,Instant-kill,varies\nCatapult Zombie,Level 5-6,651,75,2.5\nGargantuar,Level 5-8,3000,Instant-kill,4.7`,\n    sampleResult: `zombie_name/first_seen/health/damage/speed\nNormal Zombie/Level 1-1/181/100/4.7\nConehead Zombie/Level 1-3/551/100/4.7\nBuckethead Zombi/Level 1-8/1281/100/4.7\nNewspaper Zombie/Level 2-1/331/100/4.7\nFootball Zombie/Level 2-6/1581/100/2.5\nDancing Zombie/Level 2-8/335/100/1.5\nZomboni/Level 3-6/1151/Instant-kill/varies\nCatapult Zombie/Level 5-6/651/75/2.5\nGargantuar/Level 5-8/3000/Instant-kill/4.7`,\n    sampleOptions: {\n      inputSeparator: ',',\n      inputQuoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: true,\n      outputSeparator: '/',\n      outputQuoteAll: false,\n      OutputQuoteCharacter: \"'\"\n    }\n  }\n];\nexport default function ChangeCsvDelimiter({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    setResult(changeCsvSeparator(input, values));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Adjust CSV input options',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.inputSeparator}\n            onOwnChange={(val) => updateField('inputSeparator', val)}\n            description={\n              'Enter the character used to delimit columns in the CSV input file.'\n            }\n          />\n          <TextFieldWithDesc\n            value={values.inputQuoteCharacter}\n            onOwnChange={(val) => updateField('inputQuoteCharacter', val)}\n            description={\n              'Enter the quote character used to quote the CSV input fields.'\n            }\n          />\n          <TextFieldWithDesc\n            value={values.commentCharacter}\n            onOwnChange={(val) => updateField('commentCharacter', val)}\n            description={\n              'Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.'\n            }\n          />\n          <CheckboxWithDesc\n            checked={values.emptyLines}\n            onChange={(value) => updateField('emptyLines', value)}\n            title=\"Delete Lines with No Data\"\n            description=\"Remove empty lines from CSV input file.\"\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Output options',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.outputSeparator}\n            onOwnChange={(val) => updateField('outputSeparator', val)}\n            description={\n              'Enter the character used to delimit columns in the CSV output file.'\n            }\n          />\n          <CheckboxWithDesc\n            checked={values.outputQuoteAll}\n            onChange={(value) => updateField('outputQuoteAll', value)}\n            title=\"Quote All Output Fields\"\n            description=\"Wrap all fields of the output CSV file in quotes\"\n          />\n          {values.outputQuoteAll && (\n            <TextFieldWithDesc\n              value={values.OutputQuoteCharacter}\n              onOwnChange={(val) => updateField('OutputQuoteCharacter', val)}\n              description={\n                'Enter the quote character used to quote the CSV output fields.'\n              }\n            />\n          )}\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput title={'Input CSV'} value={input} onChange={setInput} />\n      }\n      resultComponent={<ToolTextResult title={'Output CSV'} value={result} />}\n      initialValues={initialValues}\n      exampleCards={exampleCards}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/csv/change-csv-separator/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('csv', {\n  path: 'change-csv-separator',\n  icon: 'material-symbols:code',\n\n  keywords: ['csv', 'separator', 'delimiter', 'change'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'csv:changeCsvSeparator.title',\n    description: 'csv:changeCsvSeparator.description',\n    shortDescription: 'csv:changeCsvSeparator.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/csv/change-csv-separator/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { splitCsv } from '@utils/csv';\n\nexport function changeCsvSeparator(\n  input: string,\n  options: InitialValuesType\n): string {\n  if (!input) return '';\n\n  const rows = splitCsv(\n    input,\n    true,\n    options.commentCharacter,\n    options.emptyLines,\n    options.inputSeparator,\n    options.inputQuoteCharacter\n  );\n\n  return rows\n    .map((row) => {\n      return row\n        .map((cell) => {\n          if (options.outputQuoteAll) {\n            return `${options.OutputQuoteCharacter}${cell}${options.OutputQuoteCharacter}`;\n          }\n          return cell;\n        })\n        .join(options.outputSeparator);\n    })\n    .join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/csv/change-csv-separator/types.ts",
    "content": "export type InitialValuesType = {\n  inputSeparator: string;\n  inputQuoteCharacter: string;\n  commentCharacter: string;\n  emptyLines: boolean;\n  outputSeparator: string;\n  outputQuoteAll: boolean;\n  OutputQuoteCharacter: string;\n};\n"
  },
  {
    "path": "src/pages/tools/csv/csv-rows-to-columns/csv-rows-to-columns.service.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { csvRowsToColumns } from './service';\n\ndescribe('csvRowsToColumns', () => {\n  it('should transpose rows to columns', () => {\n    const input = 'a,b,c\\n1,2,3\\nx,y,z';\n    const result = csvRowsToColumns(input, false, '', '#');\n    expect(result).toBe('a,1,x\\nb,2,y\\nc,3,z');\n  });\n\n  it('should fill empty values with custom filler', () => {\n    const input = 'a,b\\n1\\nx,y,z';\n    const result = csvRowsToColumns(input, false, 'FILL', '#');\n    expect(result).toBe('a,1,x\\nb,FILL,y\\nFILL,FILL,z');\n  });\n\n  it('should fill empty values with empty strings when emptyValuesFilling is true', () => {\n    const input = 'a,b\\n1\\nx,y,z';\n    const result = csvRowsToColumns(input, true, 'FILL', '#');\n    expect(result).toBe('a,1,x\\nb,,y\\n,,z');\n  });\n\n  it('should ignore rows starting with the comment character', () => {\n    const input = '#comment\\n1,2,3\\nx,y,z';\n    const result = csvRowsToColumns(input, false, '', '#');\n    expect(result).toBe('1,x\\n2,y\\n3,z');\n  });\n\n  it('should handle an empty input', () => {\n    const input = '';\n    const result = csvRowsToColumns(input, false, '', '#');\n    expect(result).toBe('');\n  });\n\n  it('should handle input with only comments', () => {\n    const input = '#comment\\n#another comment';\n    const result = csvRowsToColumns(input, false, '', '#');\n    expect(result).toBe('');\n  });\n\n  it('should handle single row input', () => {\n    const input = 'a,b,c';\n    const result = csvRowsToColumns(input, false, '', '#');\n    expect(result).toBe('a\\nb\\nc');\n  });\n\n  it('should handle single column input', () => {\n    const input = 'a\\nb\\nc';\n    const result = csvRowsToColumns(input, false, '', '#');\n    expect(result).toBe('a,b,c');\n  });\n\n  it('should handle this case onlinetools #1', () => {\n    const input =\n      'Variety,Origin\\nArabica,Ethiopia\\nRobusta,Africa\\nLiberica,Philippines\\nMocha,\\n//green tea';\n    const result = csvRowsToColumns(input, false, '1x', '//');\n    expect(result).toBe(\n      'Variety,Arabica,Robusta,Liberica,Mocha\\nOrigin,Ethiopia,Africa,Philippines,1x'\n    );\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/csv/csv-rows-to-columns/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Box } from '@mui/material';\nimport ToolContent from '@components/ToolContent';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { csvRowsToColumns } from './service';\n\nconst initialValues = {\n  emptyValuesFilling: false,\n  customFiller: 'x',\n  commentCharacter: '//'\n};\ntype InitialValuesType = typeof initialValues;\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Convert CSV Rows to Columns',\n    description:\n      'In this example, we transform the input CSV file with a single horizontal row of six values \"a,b,c,d,e,f\" into a vertical column. The program takes this row, rotates it 90 degrees, and outputs it as a column with each CSV value on a new line. This operation can also be viewed as converting a 6-dimensional row vector into a 6-dimensional column vector.',\n    sampleText: `a,b,c,d,e,f`,\n    sampleResult: `a\nb\nc\nd\ne\nf`,\n    sampleOptions: {\n      emptyValuesFilling: true,\n      customFiller: '1',\n      commentCharacter: '#'\n    }\n  },\n  {\n    title: 'Rows to Columns Transformation',\n    description:\n      'In this example, we load a CSV file containing coffee varieties and their origins. The file is quite messy, with numerous empty lines and comments, and it is hard to work with. To clean up the file, we specify the comment pattern // in the options, and the program automatically removes the comment lines from the input. Also, the empty lines are automatically removed. Once the file is cleaned up, we transform the five clean rows into five columns, each having a height of two fields.',\n    sampleText: `Variety,Origin\nArabica,Ethiopia\n\nRobusta,Africa\nLiberica,Philippines\n\nMocha,Yemen\n//green tea`,\n    sampleResult: `Variety,Arabica,Robusta,Liberica,Mocha\nOrigin,Ethiopia,Africa,Philippines,Yemen`,\n    sampleOptions: {\n      emptyValuesFilling: true,\n      customFiller: '1',\n      commentCharacter: '//'\n    }\n  },\n  {\n    title: 'Fill Missing Data',\n    description:\n      'In this example, we swap rows and columns in CSV data about team sports, the equipment used, and the number of players. The input has 5 rows and 3 columns and once rows and columns have been swapped, the output has 3 rows and 5 columns. Also notice that in the last data record, for the \"Baseball\" game, the number of players is missing. To create a fully-filled CSV, we use a custom message \"NA\", specified in the options, and fill the missing CSV field with this value.',\n    sampleText: `Sport,Equipment,Players\nBasketball,Ball,5\nFootball,Ball,11\nSoccer,Ball,11\nBaseball,Bat & Ball`,\n    sampleResult: `Sport,Basketball,Football,Soccer,Baseball\nEquipment,Ball,Ball,Ball,Bat & Ball\nPlayers,5,11,11,NA`,\n    sampleOptions: {\n      emptyValuesFilling: false,\n      customFiller: 'NA',\n      commentCharacter: '#'\n    }\n  }\n];\n\nexport default function CsvRowsToColumns({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: typeof initialValues, input: string) => {\n    setResult(\n      csvRowsToColumns(\n        input,\n        optionsValues.emptyValuesFilling,\n        optionsValues.customFiller,\n        optionsValues.commentCharacter\n      )\n    );\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Fix incomplete data',\n      component: (\n        <Box>\n          <SelectWithDesc\n            selected={values.emptyValuesFilling}\n            options={[\n              { label: 'Fill With Empty Values', value: true },\n              { label: 'Fill With Customs Values', value: false }\n            ]}\n            onChange={(value) => updateField('emptyValuesFilling', value)}\n            description={\n              'If the input CSV file is incomplete (missing values), then add empty fields or custom symbols to records to make a well-formed CSV?'\n            }\n          />\n          {!values.emptyValuesFilling && (\n            <TextFieldWithDesc\n              value={values.customFiller}\n              onOwnChange={(val) => updateField('customFiller', val)}\n              description={\n                'Use this custom value to fill in missing fields. (Works only with \"Custom Values\" mode above.)'\n              }\n            />\n          )}\n        </Box>\n      )\n    },\n    {\n      title: 'Lines with comments',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.commentCharacter}\n            onOwnChange={(val) => updateField('commentCharacter', val)}\n            description={\n              'Enter the symbol indicating the start of a comment line. (These lines are removed during conversion.)'\n            }\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/csv/csv-rows-to-columns/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('csv', {\n  i18n: {\n    name: 'csv:csvRowsToColumns.title',\n    description: 'csv:csvRowsToColumns.description',\n    shortDescription: 'csv:csvRowsToColumns.shortDescription',\n    longDescription: 'csv:csvRowsToColumns.longDescription'\n  },\n  path: 'csv-rows-to-columns',\n  icon: 'fluent:text-arrow-down-right-column-24-filled',\n  keywords: ['csv', 'rows', 'columns', 'transpose'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/csv/csv-rows-to-columns/service.ts",
    "content": "function compute(rows: string[][], columnCount: number): string[][] {\n  const result: string[][] = [];\n  for (let i = 0; i < rows.length; i++) {\n    const row = rows[i];\n    for (let j = 0; j < columnCount; j++) {\n      if (!result[j]) {\n        result[j] = [];\n      }\n      result[j][i] = row[j];\n    }\n  }\n  return result;\n}\nexport function csvRowsToColumns(\n  input: string,\n  emptyValuesFilling: boolean,\n  customFiller: string,\n  commentCharacter: string\n): string {\n  if (!input) {\n    return '';\n  }\n\n  const rows = input\n    .split('\\n')\n    .map((row) => row.split(','))\n    .filter(\n      (row) => row.length > 0 && !row[0].trim().startsWith(commentCharacter)\n    );\n  const columnCount = Math.max(...rows.map((row) => row.length));\n  for (let i = 0; i < rows.length; i++) {\n    for (let j = 0; j < columnCount; j++) {\n      if (!rows[i][j]) {\n        rows[i][j] = emptyValuesFilling ? '' : customFiller;\n      }\n    }\n  }\n  const result = compute(rows, columnCount);\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-json/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { convertCsvToJson } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { Box } from '@mui/material';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { useTranslation } from 'react-i18next';\n\ntype InitialValuesType = {\n  delimiter: string;\n  quote: string;\n  comment: string;\n  useHeaders: boolean;\n  skipEmptyLines: boolean;\n  dynamicTypes: boolean;\n  indentationType: 'tab' | 'space';\n  spacesCount: number;\n};\n\nconst initialValues: InitialValuesType = {\n  delimiter: ',',\n  quote: '\"',\n  comment: '#',\n  useHeaders: true,\n  skipEmptyLines: true,\n  dynamicTypes: true,\n  indentationType: 'space',\n  spacesCount: 2\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Basic CSV to JSON Array',\n    description: 'Convert a simple CSV file into a JSON array structure.',\n    sampleText: 'name,age,city\\nJohn,30,New York\\nAlice,25,London',\n    sampleResult: `[\n  {\n    \"name\": \"John\",\n    \"age\": 30,\n    \"city\": \"New York\"\n  },\n  {\n    \"name\": \"Alice\",\n    \"age\": 25,\n    \"city\": \"London\"\n  }\n]`,\n    sampleOptions: {\n      ...initialValues,\n      useHeaders: true,\n      dynamicTypes: true\n    }\n  },\n  {\n    title: 'CSV with Custom Delimiter',\n    description: 'Convert a CSV file that uses semicolons as separators.',\n    sampleText: 'product;price;quantity\\nApple;1.99;50\\nBanana;0.99;100',\n    sampleResult: `[\n  {\n    \"product\": \"Apple\",\n    \"price\": 1.99,\n    \"quantity\": 50\n  },\n  {\n    \"product\": \"Banana\",\n    \"price\": 0.99,\n    \"quantity\": 100\n  }\n]`,\n    sampleOptions: {\n      ...initialValues,\n      delimiter: ';'\n    }\n  },\n  {\n    title: 'CSV with Comments and Empty Lines',\n    description: 'Process CSV data while handling comments and empty lines.',\n    sampleText: `# This is a comment\nid,name,active\n1,John,true\n\n# Another comment\n2,Jane,false\n\n3,Bob,true`,\n    sampleResult: `[\n  {\n    \"id\": 1,\n    \"name\": \"John\",\n    \"active\": true\n  },\n  {\n    \"id\": 2,\n    \"name\": \"Jane\",\n    \"active\": false\n  },\n  {\n    \"id\": 3,\n    \"name\": \"Bob\",\n    \"active\": true\n  }\n]`,\n    sampleOptions: {\n      ...initialValues,\n      skipEmptyLines: true,\n      comment: '#'\n    }\n  }\n];\n\nexport default function CsvToJson({ title }: ToolComponentProps) {\n  const { t } = useTranslation('csv');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    if (input) {\n      try {\n        const jsonResult = convertCsvToJson(input, {\n          delimiter: values.delimiter,\n          quote: values.quote,\n          comment: values.comment,\n          useHeaders: values.useHeaders,\n          skipEmptyLines: values.skipEmptyLines,\n          dynamicTypes: values.dynamicTypes\n        });\n        setResult(jsonResult);\n      } catch (error) {\n        setResult(\n          `${t('csvToJson.error')}: ${\n            error instanceof Error\n              ? error.message\n              : t('csvToJson.invalidCsvFormat')\n          }`\n        );\n      }\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={initialValues}\n      compute={compute}\n      exampleCards={exampleCards}\n      inputComponent={\n        <ToolTextInput\n          title={t('csvToJson.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult\n          title={t('csvToJson.resultTitle')}\n          value={result}\n          extension={'json'}\n        />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('csvToJson.inputCsvFormat'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                description={t('csvToJson.columnSeparator')}\n                value={values.delimiter}\n                onOwnChange={(val) => updateField('delimiter', val)}\n              />\n              <TextFieldWithDesc\n                description={t('csvToJson.fieldQuote')}\n                onOwnChange={(val) => updateField('quote', val)}\n                value={values.quote}\n              />\n              <TextFieldWithDesc\n                description={t('csvToJson.commentSymbol')}\n                value={values.comment}\n                onOwnChange={(val) => updateField('comment', val)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('csvToJson.conversionOptions'),\n          component: (\n            <Box>\n              <CheckboxWithDesc\n                checked={values.useHeaders}\n                onChange={(value) => updateField('useHeaders', value)}\n                title={t('csvToJson.useHeaders')}\n                description={t('csvToJson.useHeadersDescription')}\n              />\n              <CheckboxWithDesc\n                checked={values.skipEmptyLines}\n                onChange={(value) => updateField('skipEmptyLines', value)}\n                title={t('csvToJson.skipEmptyLines')}\n                description={t('csvToJson.skipEmptyLinesDescription')}\n              />\n              <CheckboxWithDesc\n                checked={values.dynamicTypes}\n                onChange={(value) => updateField('dynamicTypes', value)}\n                title={t('csvToJson.dynamicTypes')}\n                description={t('csvToJson.dynamicTypesDescription')}\n              />\n            </Box>\n          )\n        }\n      ]}\n      toolInfo={{\n        title: t('csvToJson.toolInfo.title'),\n        description: t('csvToJson.toolInfo.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-json/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('csv', {\n  i18n: {\n    name: 'csv:csvToJson.title',\n    description: 'csv:csvToJson.description',\n    shortDescription: 'csv:csvToJson.shortDescription'\n  },\n\n  path: 'csv-to-json',\n  icon: 'lets-icons:json-light',\n\n  keywords: ['csv', 'json', 'convert', 'transform', 'parse'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-json/service.ts",
    "content": "type CsvToJsonOptions = {\n  delimiter: string;\n  quote: string;\n  comment: string;\n  useHeaders: boolean;\n  skipEmptyLines: boolean;\n  dynamicTypes: boolean;\n};\n\nconst defaultOptions: CsvToJsonOptions = {\n  delimiter: ',',\n  quote: '\"',\n  comment: '#',\n  useHeaders: true,\n  skipEmptyLines: true,\n  dynamicTypes: true\n};\n\nexport const convertCsvToJson = (\n  csv: string,\n  options: Partial<CsvToJsonOptions> = {}\n): string => {\n  const opts = { ...defaultOptions, ...options };\n  const lines = csv.split('\\n');\n  const result: any[] = [];\n  let headers: string[] = [];\n\n  // Filter out comments and empty lines\n  const validLines = lines.filter((line) => {\n    const trimmedLine = line.trim();\n    return (\n      trimmedLine &&\n      (!opts.skipEmptyLines ||\n        !containsOnlyCustomCharAndSpaces(trimmedLine, opts.delimiter)) &&\n      !trimmedLine.startsWith(opts.comment)\n    );\n  });\n\n  if (validLines.length === 0) {\n    return '[]';\n  }\n\n  // Parse headers if enabled\n  if (opts.useHeaders) {\n    headers = parseCsvLine(validLines[0], opts);\n    validLines.shift();\n  }\n\n  // Parse data lines\n  for (const line of validLines) {\n    const values = parseCsvLine(line, opts);\n\n    if (opts.useHeaders) {\n      const obj: Record<string, any> = {};\n      headers.forEach((header, i) => {\n        obj[header] = parseValue(values[i], opts.dynamicTypes);\n      });\n      result.push(obj);\n    } else {\n      result.push(values.map((v) => parseValue(v, opts.dynamicTypes)));\n    }\n  }\n\n  return JSON.stringify(result, null, 2);\n};\n\nconst parseCsvLine = (line: string, options: CsvToJsonOptions): string[] => {\n  const values: string[] = [];\n  let currentValue = '';\n  let inQuotes = false;\n\n  for (let i = 0; i < line.length; i++) {\n    const char = line[i];\n\n    if (char === options.quote) {\n      inQuotes = !inQuotes;\n    } else if (char === options.delimiter && !inQuotes) {\n      values.push(currentValue.trim());\n      currentValue = '';\n    } else {\n      currentValue += char;\n    }\n  }\n\n  values.push(currentValue.trim());\n  return values;\n};\n\nconst parseValue = (value: string, dynamicTypes: boolean): any => {\n  if (!dynamicTypes) return value;\n\n  if (value.toLowerCase() === 'true') return true;\n  if (value.toLowerCase() === 'false') return false;\n  if (value === 'null') return null;\n  if (!isNaN(Number(value))) return Number(value);\n\n  return value;\n};\n\nfunction containsOnlyCustomCharAndSpaces(str: string, customChar: string) {\n  const regex = new RegExp(`^[${customChar}\\\\s]*$`);\n  return regex.test(str);\n}\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-tsv/csv-to-tsv.service.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { csvToTsv } from './service';\n\ndescribe('csvToTsv', () => {\n  it('should convert CSV to TSV with default settings', () => {\n    const input = 'a,b,c\\n1,2,3\\n4,5,6';\n    const result = csvToTsv(input, ',', '\"', '', true, false);\n    expect(result).toBe('a\\tb\\tc\\n1\\t2\\t3\\n4\\t5\\t6');\n  });\n\n  it('should handle quoted values correctly', () => {\n    const input = '\"a\",\"b\",\"c\"\\n\"1\",\"2\",\"3\"';\n    const result = csvToTsv(input, ',', '\"', '', true, false);\n    expect(result).toBe('a\\tb\\tc\\n1\\t2\\t3');\n  });\n\n  it('should remove comments if commentCharacter is set', () => {\n    const input = '#comment\\na,b,c\\n1,2,3';\n    const result = csvToTsv(input, ',', '\"', '#', true, false);\n    expect(result).toBe('\\na\\tb\\tc\\n1\\t2\\t3');\n  });\n\n  it('should remove comments if commentCharacter is set, emptyline set to true', () => {\n    const input = '#comment\\na,b,c\\n1,2,3';\n    const result = csvToTsv(input, ',', '\"', '#', true, true);\n    expect(result).toBe('a\\tb\\tc\\n1\\t2\\t3');\n  });\n\n  it('should remove empty lines if emptyLines is true', () => {\n    const input = 'a,b,c\\n\\n1,2,3\\n\\n4,5,6';\n    const result = csvToTsv(input, ',', '\"', '', true, true);\n    expect(result).toBe('a\\tb\\tc\\n1\\t2\\t3\\n4\\t5\\t6');\n  });\n\n  it('should not unquote if value is not properly quoted', () => {\n    const input = '\"a,b,c\\n\\n1,2,3\\n\\n4,5,6';\n    const result = csvToTsv(input, ',', '\"', '', true, true);\n    expect(result).toBe('\"a\\tb\\tc\\n1\\t2\\t3\\n4\\t5\\t6');\n  });\n\n  it('should unquote if value is properly quoted', () => {\n    const input = '\"a\",b,c\\n\\n1,\"2\",3\\n\\n\"4\",5,6';\n    const result = csvToTsv(input, ',', '\"', '', true, true);\n    expect(result).toBe('a\\tb\\tc\\n1\\t2\\t3\\n4\\t5\\t6');\n  });\n\n  it('should exclude the header if header is false', () => {\n    const input = 'a,b,c\\n1,2,3\\n4,5,6';\n    const result = csvToTsv(input, ',', '\"', '', false, false);\n    expect(result).toBe('1\\t2\\t3\\n4\\t5\\t6');\n  });\n\n  it('should handle empty input gracefully', () => {\n    const input = '';\n    const result = csvToTsv(input, ',', '\"', '', true, false);\n    expect(result).toBe('');\n  });\n\n  it('should handle input with only comments and empty lines', () => {\n    const input = '#comment\\n\\n#another comment\\n';\n    const result = csvToTsv(input, ',', '\"', '#', true, true);\n    expect(result).toBe('');\n  });\n\n  it('should handle custom delimiters', () => {\n    const input = 'a|b|c\\n1|2|3\\n4|5|6';\n    const result = csvToTsv(input, '|', '\"', '', true, false);\n    expect(result).toBe('a\\tb\\tc\\n1\\t2\\t3\\n4\\t5\\t6');\n  });\n\n  it('should handle custom quote characters', () => {\n    const input = \"'a','b','c'\\n'1','2','3'\";\n    const result = csvToTsv(input, ',', \"'\", '', true, false);\n    expect(result).toBe('a\\tb\\tc\\n1\\t2\\t3');\n  });\n\n  it('should handle mixed quoted and unquoted values', () => {\n    const input = '\"a\",b,\"c\"\\n\"1\",2,\"3\"';\n    const result = csvToTsv(input, ',', '\"', '', true, false);\n    expect(result).toBe('a\\tb\\tc\\n1\\t2\\t3');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-tsv/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Box } from '@mui/material';\nimport ToolContent from '@components/ToolContent';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { csvToTsv } from './service';\n\nconst initialValues = {\n  delimiter: ',',\n  quoteCharacter: '\"',\n  commentCharacter: '#',\n  header: true,\n  emptyLines: true\n};\ntype InitialValuesType = typeof initialValues;\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Convert Game Data from the CSV Format to the TSV Format',\n    description:\n      'In this example, we transform a Comma Separated Values (CSV) file containing a leaderboard of gaming data into a Tab Separated Values (TSV) file. The input data shows the players\\' names, scores, times, and goals. We preserve the CSV column headers by enabling the \"Preserve Headers\" option and convert all data rows into TSV format. The resulting data is easier to work with as it\\'s organized in neat columns',\n    sampleText: `player_name,score,time,goals\nToniJackson,2500,30:00,15\nHenryDalton,1800,25:00,12\nDavidLee,3200,40:00,20\nEmmaJones,2100,35:00,17\nKrisDavis,1500,20:00,10`,\n    sampleResult: `player_name\tscore\ttime\tgoals\nToniJackson\t2500\t30:00\t15\nHenryDalton\t1800\t25:00\t12\nDavidLee\t3200\t40:00\t20\nEmmaJones\t2100\t35:00\t17\nKrisDavis\t1500\t20:00\t10`,\n    sampleOptions: {\n      delimiter: ',',\n      quoteCharacter: '\"',\n      commentCharacter: '#',\n      header: true,\n      emptyLines: true\n    }\n  },\n  {\n    title: 'Mythical Creatures',\n    description:\n      'In this example, we load a CSV file containing coffee varieties and their origins. The file is quite messy, with numerous empty lines and comments, and it is hard to work with. To clean up the file, we specify the comment pattern // in the options, and the program automatically removes the comment lines from the input. Also, the empty lines are automatically removed. Once the file is cleaned up, we transform the five clean rows into five columns, each having a height of two fields.',\n    sampleText: `creature;origin;habitat;powers\nUnicorn;Mythology;Forest;Magic horn\nMermaid;Mythology;Ocean;Hypnotic singing\nVampire;Mythology;Castles;Immortality\nPhoenix;Mythology;Desert;Rebirth from ashes\n\n#Dragon;Mythology;Mountains;Fire breathing\n#Werewolf;Mythology;Forests;Shape shifting`,\n    sampleResult: `Unicorn\tMythology\tForest\tMagic horn\nMermaid\tMythology\tOcean\tHypnotic singing\nVampire\tMythology\tCastles\tImmortality\nPhoenix\tMythology\tDesert\tRebirth from ashes`,\n    sampleOptions: {\n      delimiter: ';',\n      quoteCharacter: '\"',\n      commentCharacter: '#',\n      header: false,\n      emptyLines: true\n    }\n  },\n  {\n    title: 'Convert Fitness Tracker Data from CSV to TSV',\n    description:\n      'In this example, we swap rows and columns in CSV data about team sports, the equipment used, and the number of players. The input has 5 rows and 3 columns and once rows and columns have been swapped, the output has 3 rows and 5 columns. Also notice that in the last data record, for the \"Baseball\" game, the number of players is missing. To create a fully-filled CSV, we use a custom message \"NA\", specified in the options, and fill the missing CSV field with this value.',\n    sampleText: `day,steps,distance,calories\n\nMon,7500,3.75,270\nTue,12000,6.00,420\n\nWed,8000,4.00,300\nThu,9500,4.75,330\nFri,10000,5.00,350`,\n    sampleResult: `day\tsteps\tdistance\tcalories\nMon\t7500\t3.75\t270\nTue\t12000\t6.00\t420\nWed\t8000\t4.00\t300\nThu\t9500\t4.75\t330\nFri\t10000\t5.00\t350`,\n    sampleOptions: {\n      delimiter: ',',\n      quoteCharacter: '\"',\n      commentCharacter: '#',\n      header: true,\n      emptyLines: true\n    }\n  }\n];\n\nexport default function CsvToTsv({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: typeof initialValues, input: string) => {\n    setResult(\n      csvToTsv(\n        input,\n        optionsValues.delimiter,\n        optionsValues.quoteCharacter,\n        optionsValues.commentCharacter,\n        optionsValues.header,\n        optionsValues.emptyLines\n      )\n    );\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'CSV Format Options',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.delimiter}\n            onOwnChange={(val) => updateField('delimiter', val)}\n            description={\n              'Enter the character used to delimit columns in the CSV file.'\n            }\n          />\n          <TextFieldWithDesc\n            value={values.quoteCharacter}\n            onOwnChange={(val) => updateField('quoteCharacter', val)}\n            description={\n              'Enter the quote character used to quote the CSV fields.'\n            }\n          />\n          <TextFieldWithDesc\n            value={values.commentCharacter}\n            onOwnChange={(val) => updateField('commentCharacter', val)}\n            description={\n              'Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.'\n            }\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Conversion Options',\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            checked={values.header}\n            onChange={(value) => updateField('header', value)}\n            title=\"Use Headers\"\n            description=\"Keep the first row as column names.\"\n          />\n          <CheckboxWithDesc\n            checked={values.emptyLines}\n            onChange={(value) => updateField('emptyLines', value)}\n            title=\"Ignore Lines with No Data\"\n            description=\"Enable to prevent the conversion of empty lines in the input CSV file.\"\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-tsv/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('csv', {\n  i18n: {\n    name: 'csv:csvToTsv.title',\n    description: 'csv:csvToTsv.description',\n    shortDescription: 'csv:csvToTsv.shortDescription',\n    longDescription: 'csv:csvToTsv.longDescription'\n  },\n\n  path: 'csv-to-tsv',\n  icon: 'codicon:keyboard-tab',\n  keywords: ['csv', 'tsv', 'convert', 'transform', 'parse'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-tsv/service.ts",
    "content": "import { unquoteIfQuoted } from '@utils/string';\nexport function csvToTsv(\n  input: string,\n  delimiter: string,\n  quoteCharacter: string,\n  commentCharacter: string,\n  header: boolean,\n  emptyLines: boolean\n): string {\n  // edge case: if input is empty, return empty string\n  if (!input) return '';\n  const lines = input.split('\\n');\n  // is header is set to false, remove the first line\n  if (!header) lines.shift();\n\n  const tsvLines = lines.map((line) => {\n    //  if comment character is set, remove the lines that start with it\n    if (commentCharacter && line.startsWith(commentCharacter)) return '';\n    const cells = line.split(delimiter);\n    cells.forEach((cell, index) => {\n      cells[index] = unquoteIfQuoted(cell, quoteCharacter);\n    });\n    return cells.join('\\t');\n  });\n  // if empty lines is set to true, remove the empty lines\n\n  return !emptyLines\n    ? tsvLines.join('\\n')\n    : tsvLines.filter((line) => line.trim() !== '').join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-xml/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { convertCsvToXml } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { Box } from '@mui/material';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\n\ntype InitialValuesType = {\n  delimiter: string;\n  quote: string;\n  comment: string;\n  useHeaders: boolean;\n  skipEmptyLines: boolean;\n};\n\nconst initialValues: InitialValuesType = {\n  delimiter: ',',\n  quote: '\"',\n  comment: '#',\n  useHeaders: true,\n  skipEmptyLines: true\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Basic CSV to XML',\n    description: 'Convert a simple CSV file into an XML format.',\n    sampleText: 'name,age,city\\nJohn,30,New York\\nAlice,25,London',\n    sampleResult: `<root>\n  <row>\n    <name>John</name>\n    <age>30</age>\n    <city>New York</city>\n  </row>\n  <row>\n    <name>Alice</name>\n    <age>25</age>\n    <city>London</city>\n  </row>\n</root>`,\n    sampleOptions: {\n      ...initialValues,\n      useHeaders: true\n    }\n  }\n];\n\nexport default function CsvToXml({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    if (input) {\n      try {\n        const xmlResult = convertCsvToXml(input, values);\n        setResult(xmlResult);\n      } catch (error) {\n        setResult(\n          `Error: ${\n            error instanceof Error ? error.message : 'Invalid CSV format'\n          }`\n        );\n      }\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={initialValues}\n      compute={compute}\n      exampleCards={exampleCards}\n      inputComponent={\n        <ToolTextInput title=\"Input CSV\" value={input} onChange={setInput} />\n      }\n      resultComponent={\n        <ToolTextResult title=\"Output XML\" value={result} extension={'xml'} />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: 'Input CSV Format',\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                description=\"Column Separator\"\n                value={values.delimiter}\n                onOwnChange={(val) => updateField('delimiter', val)}\n              />\n              <TextFieldWithDesc\n                description=\"Field Quote\"\n                onOwnChange={(val) => updateField('quote', val)}\n                value={values.quote}\n              />\n              <TextFieldWithDesc\n                description=\"Comment Symbol\"\n                value={values.comment}\n                onOwnChange={(val) => updateField('comment', val)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: 'Conversion Options',\n          component: (\n            <Box>\n              <CheckboxWithDesc\n                checked={values.useHeaders}\n                onChange={(value) => updateField('useHeaders', value)}\n                title=\"Use Headers\"\n                description=\"First row is treated as column headers\"\n              />\n              <CheckboxWithDesc\n                checked={values.skipEmptyLines}\n                onChange={(value) => updateField('skipEmptyLines', value)}\n                title=\"Skip Empty Lines\"\n                description=\"Don't process empty lines in the CSV\"\n              />\n            </Box>\n          )\n        }\n      ]}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-xml/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('csv', {\n  i18n: {\n    name: 'csv:csvToXml.title',\n    description: 'csv:csvToXml.description',\n    shortDescription: 'csv:csvToXml.shortDescription'\n  },\n\n  path: 'csv-to-xml',\n  icon: 'mdi-light:xml',\n\n  keywords: ['convert', 'transform', 'parse'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-xml/service.ts",
    "content": "type CsvToXmlOptions = {\n  delimiter: string;\n  quote: string;\n  comment: string;\n  useHeaders: boolean;\n  skipEmptyLines: boolean;\n};\n\nexport const convertCsvToXml = (\n  csv: string,\n  options: CsvToXmlOptions\n): string => {\n  const lines = csv.split('\\n').map((line) => line.trim());\n\n  let xmlResult = `<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\\n<root>\\n`;\n  let headers: string[] = [];\n\n  const validLines = lines.filter(\n    (line) =>\n      line &&\n      !line.startsWith(options.comment) &&\n      (!options.skipEmptyLines || line.trim() !== '')\n  );\n\n  if (validLines.length === 0) {\n    return `<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\\n<root></root>`;\n  }\n\n  if (options.useHeaders) {\n    headers = parseCsvLine(validLines[0], options);\n    validLines.shift();\n  }\n\n  validLines.forEach((line, index) => {\n    const values = parseCsvLine(line, options);\n    xmlResult += `  <row id=\"${index}\">\\n`;\n    headers.forEach((header, i) => {\n      xmlResult += `    <${header}>${values[i] || ''}</${header}>\\n`;\n    });\n    xmlResult += `  </row>\\n`;\n  });\n\n  xmlResult += `</root>`;\n  return xmlResult;\n};\n\nconst parseCsvLine = (line: string, options: CsvToXmlOptions): string[] => {\n  const values: string[] = [];\n  let currentValue = '';\n  let inQuotes = false;\n\n  for (let i = 0; i < line.length; i++) {\n    const char = line[i];\n\n    if (char === options.quote) {\n      inQuotes = !inQuotes;\n    } else if (char === options.delimiter && !inQuotes) {\n      values.push(currentValue.trim());\n      currentValue = '';\n    } else {\n      currentValue += char;\n    }\n  }\n\n  values.push(currentValue.trim());\n  return values;\n};\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-yaml/csv-to-yaml.service.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { main } from './service';\nimport { InitialValuesType } from './types';\n\n// filepath: c:\\CODE\\omni-tools\\src\\pages\\tools\\csv\\csv-to-yaml\\csv-to-yaml.service.test.ts\ndescribe('main', () => {\n  const defaultOptions: InitialValuesType = {\n    csvSeparator: ',',\n    quoteCharacter: '\"',\n    commentCharacter: '#',\n    emptyLines: false,\n    headerRow: true,\n    spaces: 2\n  };\n\n  it('should return empty string for empty input', () => {\n    const result = main('', defaultOptions);\n    expect(result).toEqual('');\n  });\n\n  it('should return this if header is set to false', () => {\n    const options = { ...defaultOptions, headerRow: false };\n    const result = main('John,30\\nEmma,50', options);\n    expect(result).toEqual('-\\n  - John\\n  - 30\\n-\\n  - Emma\\n  - 50');\n  });\n\n  it('should return this header is set to true', () => {\n    const options = { ...defaultOptions };\n    const result = main('Name,Age\\nJohn,30\\nEmma,50', options);\n    expect(result).toEqual(\n      '-\\n  Name: John\\n  Age: 30\\n-\\n  Name: Emma\\n  Age: 50'\n    );\n  });\n\n  it('should return this header is set to true and comment flag set', () => {\n    const options = { ...defaultOptions, commentcharacter: '#' };\n    const result = main('Name,Age\\nJohn,30\\n#Emma,50', options);\n    expect(result).toEqual('-\\n  Name: John\\n  Age: 30');\n  });\n\n  it('should return this header is set to true and spaces is set to 3', () => {\n    const options = { ...defaultOptions, spaces: 3 };\n    const result = main('Name,Age\\nJohn,30\\nEmma,50', options);\n    expect(result).toEqual(\n      '-\\n   Name: John\\n   Age: 30\\n-\\n   Name: Emma\\n   Age: 50'\n    );\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-yaml/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { main } from './service';\nimport { InitialValuesType } from './types';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\n\nconst initialValues: InitialValuesType = {\n  csvSeparator: ',',\n  quoteCharacter: '\"',\n  commentCharacter: '#',\n  emptyLines: true,\n  headerRow: true,\n  spaces: 2\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Convert Music Playlist CSV to YAML',\n    description:\n      'In this example, we transform a short CSV file containing a music playlist into structured YAML data. The input CSV contains five records with three columns each and the output YAML contains five lists of lists (one list for each CSV record). In YAML, lists start with the \"-\" symbol and the nested lists are indented with two spaces',\n    sampleText: `The Beatles,\"Yesterday\",Pop Rock\nQueen,\"Bohemian Rhapsody\",Rock\nNirvana,\"Smells Like Teen Spirit\",Grunge\nMichael Jackson,\"Billie Jean\",Pop\nStevie Wonder,\"Superstition\",Funk`,\n    sampleResult: `-\n  - The Beatles\n  - Yesterday\n  - Pop Rock\n- \n  - Queen\n  - Bohemian Rhapsody\n  - Rock\n- \n  - Nirvana\n  - Smells Like Teen Spirit\n  - Grunge\n- \n  - Michael Jackson\n  - Billie Jean\n  - Pop\n-\n  - Stevie Wonder\n  - Superstition\n  - Funk`,\n    sampleOptions: {\n      ...initialValues,\n      headerRow: false\n    }\n  },\n  {\n    title: 'Planetary CSV Data',\n    description:\n      'In this example, we are working with CSV data that summarizes key properties of three planets in our solar system. The data consists of three columns with headers \"planet\", \"relative mass\" (with \"1\" being the mass of earth), and \"satellites\". To preserve the header names in the output YAML data, we enable the \"Transform Headers\" option, creating a YAML file that contains a list of YAML objects, where each object has three keys: \"planet\", \"relative mass\", and \"satellites\".',\n    sampleText: `planet,relative mass,satellites\nVenus,0.815,0\nEarth,1.000,1\nMars,0.107,2`,\n    sampleResult: `-\n  planet: Venus\n  relative mass: 0.815\n  satellites: '0'\n- \n  planet: Earth\n  relative mass: 1.000\n  satellites: '1'\n- \n  planet: Mars\n  relative mass: 0.107\n  satellites: '2'`,\n    sampleOptions: {\n      ...initialValues\n    }\n  },\n  {\n    title: 'Convert Non-standard CSV to YAML',\n    description:\n      'In this example, we convert a CSV file with non-standard formatting into a regular YAML file. The input data uses a semicolon as a separator for the \"product\", \"quantity\", and \"price\" fields. It also contains empty lines and lines that are commented out. To make the program work with this custom CSV file, we input the semicolon symbol in the CSV delimiter options. To skip comments, we specify \"#\" as the symbol that starts comments. And to remove empty lines, we activate the option for skipping blank lines (that do not contain any symbols). In the output, we obtain a YAML file that contains a list of three objects, which use CSV headers as keys. Additionally, the objects in the YAML file are indented with four spaces.',\n    sampleText: `item;quantity;price\nmilk;2;3.50\n\n#eggs;12;2.99\nbread;1;4.25\n#apples;4;1.99\ncheese;1;8.99`,\n    sampleResult: `-\n  item: milk\n  quantity: 2\n  price: 3.50\n-\n  item: bread\n  quantity: 1\n  price: 4.25\n-\n  item: cheese\n  quantity: 1\n  price: 8.99`,\n    sampleOptions: {\n      ...initialValues,\n      csvSeparator: ';'\n    }\n  }\n];\nexport default function CsvToYaml({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: string) => {\n    setResult(main(input, optionsValues));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Adjust CSV input',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.csvSeparator}\n            onOwnChange={(val) => updateField('csvSeparator', val)}\n            description={\n              'Enter the character used to delimit columns in the CSV file.'\n            }\n          />\n          <TextFieldWithDesc\n            value={values.quoteCharacter}\n            onOwnChange={(val) => updateField('quoteCharacter', val)}\n            description={\n              'Enter the quote character used to quote the CSV fields.'\n            }\n          />\n          <TextFieldWithDesc\n            value={values.commentCharacter}\n            onOwnChange={(val) => updateField('commentCharacter', val)}\n            description={\n              'Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.'\n            }\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Conversion Options',\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            checked={values.headerRow}\n            onChange={(value) => updateField('headerRow', value)}\n            title=\"Use Headers\"\n            description=\"Keep the first row as column names.\"\n          />\n          <CheckboxWithDesc\n            checked={values.emptyLines}\n            onChange={(value) => updateField('emptyLines', value)}\n            title=\"Ignore Lines with No Data\"\n            description=\"Enable to prevent the conversion of empty lines in the input CSV file.\"\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Adjust YAML indentation',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.spaces}\n            type=\"number\"\n            onOwnChange={(val) => updateField('spaces', Number(val))}\n            inputProps={{ min: 1 }}\n            description={\n              'Set the number of spaces to use for YAML indentation.'\n            }\n          />\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput title={'Input CSV'} value={input} onChange={setInput} />\n      }\n      resultComponent={<ToolTextResult title={'Output YAML'} value={result} />}\n      initialValues={initialValues}\n      exampleCards={exampleCards}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-yaml/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('csv', {\n  i18n: {\n    name: 'csv:csvToYaml.title',\n    description: 'csv:csvToYaml.description',\n    shortDescription: 'csv:csvToYaml.shortDescription',\n    longDescription: 'csv:csvToYaml.longDescription'\n  },\n\n  path: 'csv-to-yaml',\n  icon: 'nonicons:yaml-16',\n  keywords: ['comma', 'separated', 'values', 'transform', 'markup', 'convert'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-yaml/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { getCsvHeaders, splitCsv } from '@utils/csv';\nimport { unquoteIfQuoted } from '@utils/string';\n\nfunction toYaml(\n  input: Record<string, string>[] | string[][],\n  indentSpaces: number = 2\n): string {\n  if (indentSpaces == 0) {\n    throw new Error('Indent spaces must be greater than zero');\n  }\n  const indent = ' '.repeat(indentSpaces);\n\n  if (\n    Array.isArray(input) &&\n    input.length > 0 &&\n    typeof input[0] === 'object' &&\n    !Array.isArray(input[0])\n  ) {\n    return (input as Record<string, string>[])\n      .map((obj) => {\n        const lines = Object.entries(obj)\n          .map(([key, value]) => `${indent}${key}: ${value}`)\n          .join('\\n');\n        return `-\\n${lines}`;\n      })\n      .join('\\n');\n  }\n\n  // If input is string[][].\n  if (Array.isArray(input) && Array.isArray(input[0])) {\n    return (input as string[][])\n      .map((row) => {\n        const inner = row.map((cell) => `${indent}- ${cell}`).join('\\n');\n        return `-\\n${inner}`;\n      })\n      .join('\\n');\n  }\n\n  return 'invalid input';\n}\n\nexport function main(input: string, options: InitialValuesType): string {\n  if (!input) {\n    return '';\n  }\n\n  const rows = splitCsv(\n    input,\n    true,\n    options.commentCharacter,\n    options.emptyLines,\n    options.csvSeparator,\n    options.quoteCharacter\n  );\n\n  rows.forEach((row) => {\n    row.forEach((cell, cellIndex) => {\n      row[cellIndex] = unquoteIfQuoted(cell, options.quoteCharacter);\n    });\n  });\n\n  if (options.headerRow) {\n    const headerRow = getCsvHeaders(\n      input,\n      options.csvSeparator,\n      options.quoteCharacter,\n      options.commentCharacter\n    );\n    headerRow.forEach((header, headerIndex) => {\n      headerRow[headerIndex] = unquoteIfQuoted(header, options.quoteCharacter);\n    });\n\n    const result: Record<string, string>[] = rows.slice(1).map((row) => {\n      const entry: Record<string, string> = {};\n      headerRow.forEach((header, headerIndex) => {\n        entry[header] = row[headerIndex] ?? '';\n      });\n      return entry;\n    });\n    return toYaml(result, options.spaces);\n  }\n\n  return toYaml(rows, options.spaces);\n}\n"
  },
  {
    "path": "src/pages/tools/csv/csv-to-yaml/types.ts",
    "content": "export type InitialValuesType = {\n  csvSeparator: string;\n  quoteCharacter: string;\n  commentCharacter: string;\n  emptyLines: boolean;\n  headerRow: boolean;\n  spaces: number;\n};\n"
  },
  {
    "path": "src/pages/tools/csv/find-incomplete-csv-records/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { findIncompleteCsvRecords } from './service';\nimport { InitialValuesType } from './types';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  csvSeparator: ',',\n  quoteCharacter: '\"',\n  commentCharacter: '#',\n  emptyLines: true,\n  emptyValues: true,\n  messageLimit: false,\n  messageNumber: 10\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'CSV Completeness Check',\n    description:\n      'In this example, we upload a simple CSV file containing names, surnames, and dates of birth. The tool analyzes the data and displays a green \"Complete CSV\" badge as it finds that there are no missing values or empty records. To say it differently, this check confirms that all rows and columns have the expected number of values in the data and the file is ready for use in any software that imports CSV files without hiccups.',\n    sampleText: `name,surname,dob\nJohn,Warner,1990-05-15\nLily,Meadows,1985-12-20\nJaime,Crane,1993-01-23\nJeri,Carroll,2000-11-07\nSimon,Harper,2013-04-10`,\n    sampleResult: `The Csv input is complete.`,\n    sampleOptions: {\n      csvSeparator: ',',\n      quoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: true,\n      emptyValues: true,\n      messageLimit: false,\n      messageNumber: 10\n    }\n  },\n  {\n    title: 'Find Missing Fields in Broken CSV',\n    description:\n      'In this example, we find the missing fields in a CSV file containing city names, time zones, and standard time information. As a result of the analysis, we see a red badge in the output and a text list of missing values in the dataset. The file has missing values on two rows: row 3 lacks standard time data (column 3), and row 5 lacks time zone and standard time data (columns 2 and 3).',\n    sampleText: `City,Time Zone,Standard Time\nLondon,UTC+00:00,GMT\nChicago,UTC-06:00\nTokyo,UTC+09:00,JST\nSydney\nBerlin,UTC+01:00,CET`,\n    sampleResult: `Title: Found missing column(s) on line 3\nMessage: Line 3 has 1 missing column(s).\n\nTitle: Found missing column(s) on line 5\nMessage: Line 5 has 2 missing column(s).`,\n    sampleOptions: {\n      csvSeparator: ',',\n      quoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: true,\n      emptyValues: false,\n      messageLimit: true,\n      messageNumber: 10\n    }\n  },\n  {\n    title: 'Detect Empty and Missing Values',\n    description:\n      'This example checks a data file containing information astronomical data about constellations. Not only does it find incomplete records but also detects all empty fields by activating the \"Find Empty Values\" checkbox. The empty fields are those that have zero length or contain just whitespace. Such fields contain no information. Additionally, since this file uses semicolons instead of commas for separators, we specify the \";\" symbol in the options to make the program work with SSV (Semicolon-Separated Values) data. As a result, the program identifies three empty fields and one row with missing data.',\n    sampleText: `Abbreviation;Constellation;Main stars\n\nCas;Cassiopeia;5\nCep;Cepheus;7\n;Andromeda;16\n\nCyg;;\nDel;Delphinus`,\n    sampleResult: `Title: Found missing values on line 4\nMessage: Empty values on line 4: column 1.\n\nTitle: Found missing values on line 5\nMessage: Empty values on line 5: column 2, column 3.\n\nTitle: Found missing column(s) on line 6\nMessage: Line 6 has 1 missing column(s).`,\n    sampleOptions: {\n      csvSeparator: ';',\n      quoteCharacter: '\"',\n      commentCharacter: '#',\n      emptyLines: true,\n      emptyValues: true,\n      messageLimit: true,\n      messageNumber: 10\n    }\n  }\n];\nexport default function FindIncompleteCsvRecords({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('csv');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    setResult(findIncompleteCsvRecords(input, values));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('findIncompleteCsvRecords.csvInputOptions'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.csvSeparator}\n            onOwnChange={(val) => updateField('csvSeparator', val)}\n            description={t('findIncompleteCsvRecords.csvSeparatorDescription')}\n          />\n          <TextFieldWithDesc\n            value={values.quoteCharacter}\n            onOwnChange={(val) => updateField('quoteCharacter', val)}\n            description={t(\n              'findIncompleteCsvRecords.quoteCharacterDescription'\n            )}\n          />\n          <TextFieldWithDesc\n            value={values.commentCharacter}\n            onOwnChange={(val) => updateField('commentCharacter', val)}\n            description={t(\n              'findIncompleteCsvRecords.commentCharacterDescription'\n            )}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('findIncompleteCsvRecords.checkingOptions'),\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            checked={values.emptyLines}\n            onChange={(value) => updateField('emptyLines', value)}\n            title={t('findIncompleteCsvRecords.deleteLinesWithNoData')}\n            description={t(\n              'findIncompleteCsvRecords.deleteLinesWithNoDataDescription'\n            )}\n          />\n\n          <CheckboxWithDesc\n            checked={values.emptyValues}\n            onChange={(value) => updateField('emptyValues', value)}\n            title={t('findIncompleteCsvRecords.findEmptyValues')}\n            description={t(\n              'findIncompleteCsvRecords.findEmptyValuesDescription'\n            )}\n          />\n\n          <CheckboxWithDesc\n            checked={values.messageLimit}\n            onChange={(value) => updateField('messageLimit', value)}\n            title={t('findIncompleteCsvRecords.limitNumberOfMessages')}\n          />\n\n          {values.messageLimit && (\n            <TextFieldWithDesc\n              value={values.messageNumber}\n              onOwnChange={(val) => updateField('messageNumber', Number(val))}\n              type=\"number\"\n              inputProps={{ min: 1 }}\n              description={t(\n                'findIncompleteCsvRecords.messageLimitDescription'\n              )}\n            />\n          )}\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput\n          title={t('findIncompleteCsvRecords.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult\n          title={t('findIncompleteCsvRecords.resultTitle')}\n          value={result}\n        />\n      }\n      initialValues={initialValues}\n      exampleCards={exampleCards}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('findIncompleteCsvRecords.toolInfo.title', { title }),\n        description: longDescription\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/csv/find-incomplete-csv-records/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('csv', {\n  path: 'find-incomplete-csv-records',\n  icon: 'tdesign:search-error',\n\n  keywords: ['find', 'incomplete', 'csv', 'records'],\n\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'csv:findIncompleteCsvRecords.title',\n    description: 'csv:findIncompleteCsvRecords.description',\n    shortDescription: 'csv:findIncompleteCsvRecords.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/csv/find-incomplete-csv-records/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { splitCsv } from '@utils/csv';\n\nfunction generateMessage(\n  row: string[],\n  lineIndex: number,\n  maxLength: number,\n  emptyLines: boolean,\n  emptyValues: boolean\n) {\n  const lineNumber = lineIndex + 1;\n  // check if empty lines are allowed\n  if (!emptyLines && row.length === 1 && row[0] === '')\n    return { title: 'Missing Line', message: `Line ${lineNumber} is empty.` };\n\n  // if row legth is less than maxLength it means that there are missing columns\n  if (row.length < maxLength)\n    return {\n      title: `Found missing column(s) on line ${lineNumber}`,\n      message: `Line ${lineNumber} has ${\n        maxLength - row.length\n      } missing column(s).`\n    };\n\n  // if row length is equal to maxLength we should check if there are empty values\n  if (row.length == maxLength && emptyValues) {\n    let missingValues = false;\n    let message = `Empty values on line ${lineNumber}: `;\n    row.forEach((cell, index) => {\n      if (cell.trim() === '') {\n        missingValues = true;\n        message += `column ${index + 1}, `;\n      }\n    });\n    if (missingValues)\n      return {\n        title: `Found missing values on line ${lineNumber}`,\n        message: message.slice(0, -2) + '.'\n      };\n  }\n\n  return null;\n}\nexport function findIncompleteCsvRecords(\n  input: string,\n  options: InitialValuesType\n): string {\n  if (!input) return '';\n\n  if (options.messageLimit && options.messageNumber <= 0)\n    throw new Error('Message number must be greater than 0');\n\n  const rows = splitCsv(\n    input,\n    true,\n    options.commentCharacter,\n    options.emptyLines,\n    options.csvSeparator,\n    options.quoteCharacter\n  );\n  const maxLength = Math.max(...rows.map((row) => row.length));\n  const messages = rows\n    .map((row, index) =>\n      generateMessage(\n        row,\n        index,\n        maxLength,\n        options.emptyLines,\n        options.emptyValues\n      )\n    )\n    .filter(Boolean)\n    .map((msg) => `Title: ${msg!.title}\\nMessage: ${msg!.message}`);\n\n  return messages.length > 0\n    ? options.messageLimit\n      ? messages.slice(0, options.messageNumber).join('\\n\\n')\n      : messages.join('\\n\\n')\n    : 'The Csv input is complete.';\n}\n"
  },
  {
    "path": "src/pages/tools/csv/find-incomplete-csv-records/types.ts",
    "content": "export type InitialValuesType = {\n  csvSeparator: string;\n  quoteCharacter: string;\n  commentCharacter: string;\n  emptyLines: boolean;\n  emptyValues: boolean;\n  messageLimit: boolean;\n  messageNumber: number;\n};\n"
  },
  {
    "path": "src/pages/tools/csv/index.ts",
    "content": "import { tool as insertCsvColumns } from './insert-csv-columns/meta';\nimport { tool as transposeCsv } from './transpose-csv/meta';\nimport { tool as findIncompleteCsvRecords } from './find-incomplete-csv-records/meta';\nimport { tool as ChangeCsvDelimiter } from './change-csv-separator/meta';\nimport { tool as csvToYaml } from './csv-to-yaml/meta';\nimport { tool as csvToJson } from './csv-to-json/meta';\nimport { tool as csvToXml } from './csv-to-xml/meta';\nimport { tool as csvToRowsColumns } from './csv-rows-to-columns/meta';\nimport { tool as csvToTsv } from './csv-to-tsv/meta';\nimport { tool as tsvToJson } from './tsv-to-json/meta';\nimport { tool as swapCsvColumns } from './swap-csv-columns/meta';\n\nexport const csvTools = [\n  csvToJson,\n  csvToXml,\n  csvToRowsColumns,\n  csvToTsv,\n  swapCsvColumns,\n  csvToYaml,\n  ChangeCsvDelimiter,\n  findIncompleteCsvRecords,\n  transposeCsv,\n  insertCsvColumns,\n  tsvToJson\n];\n"
  },
  {
    "path": "src/pages/tools/csv/insert-csv-columns/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { main } from './service';\nimport { InitialValuesType } from './types';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { getCsvHeaders } from '@utils/csv';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  csvToInsert: '',\n  commentCharacter: '#',\n  separator: ',',\n  quoteChar: '\"',\n  insertingPosition: 'append',\n  customFill: false,\n  customFillValue: '',\n  customPostionOptions: 'headerName',\n  headerName: '',\n  rowNumber: 1\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Insert a single column by position',\n    description:\n      'In this example, we insert a single column \"city\" at position 1 in the CSV data. The input CSV has data about cars, including the \"Brand\" and \"Model\" of the car. We now add a \"city\" column at position 1. To do this, we enter the city data in the comma-separated format in the \"New Column\" option, and to quickly add the new column at position 1, then we specify the position number.',\n    sampleText: `Brand,Model\nToyota,Camry\nFord,Mustang\nHonda,Accord\nChevrolet,Malibu`,\n    sampleResult: `city,Brand,Model\ndallas,Toyota,Camry\nhouston,Ford,Mustang\ndallas,Honda,Accord\nhouston,Chevrolet,Malibu`,\n    sampleOptions: {\n      csvToInsert: `city\ndallas\nhouston`,\n      commentCharacter: '#',\n      separator: ',',\n      quoteChar: '\"',\n      insertingPosition: 'custom',\n      customFill: true,\n      customFillValue: 'k',\n      customPostionOptions: 'rowNumber',\n      headerName: '',\n      rowNumber: 1\n    }\n  },\n  {\n    title: 'Append Multiple columns by header Name',\n    description:\n      'In this example, we append two data columns to the end of CSV data. The input CSV has data about cars, including the \"Brand\" and \"Model\" of the car. We now add two more columns at the end: \"Year\" and \"Price\". To do this, we enter these two data columns in the comma-separated format in the \"New Column\" option, and to quickly add the new columns to the end of the CSV, then we specify the name of the header they should be put after.',\n    sampleText: `Brand,Model\nToyota,Camry\nFord,Mustang\nHonda,Accord\nChevrolet,Malibu`,\n    sampleResult: `Brand,Model,Year,Price\nToyota,Camry,2022,25000\nFord,Mustang,2021,35000\nHonda,Accord,2022,27000\nChevrolet,Malibu,2021,28000`,\n    sampleOptions: {\n      csvToInsert: `Year,Price\n2022,25000\n2021,35000\n2022,27000\n2021,28000`,\n      commentCharacter: '#',\n      separator: ',',\n      quoteChar: '\"',\n      insertingPosition: 'custom',\n      customFill: false,\n      customFillValue: 'x',\n      customPostionOptions: 'headerName',\n      headerName: 'Model',\n      rowNumber: 1\n    }\n  },\n  {\n    title: 'Append Multiple columns',\n    description:\n      'In this example, we append two data columns to the end of CSV data. The input CSV has data about cars, including the \"Brand\" and \"Model\" of the car. We now add two more columns at the end: \"Year\" and \"Price\". To do this, we enter these two data columns in the comma-separated format in the \"New Column\" option, and to quickly add the new columns to the end of the CSV, then we select append.',\n    sampleText: `Brand,Model\nToyota,Camry\nFord,Mustang\nHonda,Accord\nChevrolet,Malibu`,\n    sampleResult: `Brand,Model,Year,Price\nToyota,Camry,2022,25000\nFord,Mustang,2021,35000\nHonda,Accord,2022,27000\nChevrolet,Malibu,2021,28000`,\n    sampleOptions: {\n      csvToInsert: `Year,Price\n2022,25000\n2021,35000\n2022,27000\n2021,28000`,\n      commentCharacter: '#',\n      separator: ',',\n      quoteChar: '\"',\n      insertingPosition: 'append',\n      customFill: false,\n      customFillValue: 'x',\n      customPostionOptions: 'rowNumber',\n      headerName: '',\n      rowNumber: 1\n    }\n  }\n];\nexport default function InsertCsvColumns({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('csv');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    setResult(main(input, values));\n  };\n\n  const headers = getCsvHeaders(input);\n  const headerOptions =\n    headers.length > 0\n      ? headers.map((item) => ({\n          label: `${item}`,\n          value: item\n        }))\n      : [];\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('insertCsvColumns.csvToInsert'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            multiline\n            rows={3}\n            value={values.csvToInsert}\n            onOwnChange={(val) => updateField('csvToInsert', val)}\n            title={t('insertCsvColumns.csvSeparator')}\n            description={t('insertCsvColumns.csvToInsertDescription')}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('insertCsvColumns.csvOptions'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.separator}\n            onOwnChange={(val) => updateField('separator', val)}\n            description={t('insertCsvColumns.separatorDescription')}\n          />\n          <TextFieldWithDesc\n            value={values.quoteChar}\n            onOwnChange={(val) => updateField('quoteChar', val)}\n            description={t('insertCsvColumns.quoteCharDescription')}\n          />\n          <TextFieldWithDesc\n            value={values.commentCharacter}\n            onOwnChange={(val) => updateField('commentCharacter', val)}\n            description={t('insertCsvColumns.commentCharacterDescription')}\n          />\n          <SelectWithDesc\n            selected={values.customFill}\n            options={[\n              {\n                label: t('insertCsvColumns.fillWithEmptyValues'),\n                value: false\n              },\n              {\n                label: t('insertCsvColumns.fillWithCustomValues'),\n                value: true\n              }\n            ]}\n            onChange={(value) => {\n              updateField('customFill', value);\n              if (!value) {\n                updateField('customFillValue', ''); // Reset custom fill value\n              }\n            }}\n            description={t('insertCsvColumns.customFillDescription')}\n          />\n          {values.customFill && (\n            <TextFieldWithDesc\n              value={values.customFillValue}\n              onOwnChange={(val) => updateField('customFillValue', val)}\n              description={t('insertCsvColumns.customFillValueDescription')}\n            />\n          )}\n        </Box>\n      )\n    },\n    {\n      title: t('insertCsvColumns.positionOptions'),\n      component: (\n        <Box>\n          <SelectWithDesc\n            selected={values.insertingPosition}\n            options={[\n              {\n                label: t('insertCsvColumns.prependColumns'),\n                value: 'prepend'\n              },\n              {\n                label: t('insertCsvColumns.appendColumns'),\n                value: 'append'\n              },\n              {\n                label: t('insertCsvColumns.customPosition'),\n                value: 'custom'\n              }\n            ]}\n            onChange={(value) => updateField('insertingPosition', value)}\n            description={t('insertCsvColumns.insertingPositionDescription')}\n          />\n\n          {values.insertingPosition === 'custom' && (\n            <SelectWithDesc\n              selected={values.customPostionOptions}\n              options={[\n                {\n                  label: t('insertCsvColumns.headerName'),\n                  value: 'headerName'\n                },\n                {\n                  label: t('insertCsvColumns.position'),\n                  value: 'rowNumber'\n                }\n              ]}\n              onChange={(value) => updateField('customPostionOptions', value)}\n              description={t(\n                'insertCsvColumns.customPositionOptionsDescription'\n              )}\n            />\n          )}\n\n          {values.insertingPosition === 'custom' &&\n            values.customPostionOptions === 'headerName' && (\n              <SelectWithDesc\n                selected={values.headerName}\n                options={headerOptions}\n                onChange={(value) => updateField('headerName', value)}\n                description={t('insertCsvColumns.headerNameDescription')}\n              />\n            )}\n\n          {values.insertingPosition === 'custom' &&\n            values.customPostionOptions === 'rowNumber' && (\n              <TextFieldWithDesc\n                value={values.rowNumber.toString()}\n                onOwnChange={(val) =>\n                  updateField('rowNumber', parseInt(val) || 0)\n                }\n                description={t('insertCsvColumns.rowNumberDescription')}\n                type=\"number\"\n              />\n            )}\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolTextInput\n          title={t('insertCsvColumns.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult\n          title={t('insertCsvColumns.resultTitle')}\n          value={result}\n          extension={'csv'}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      toolInfo={{\n        title: t('insertCsvColumns.toolInfo.title'),\n        description: t('insertCsvColumns.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/csv/insert-csv-columns/insert-csv-columns.service.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { main } from './service';\nimport type { InitialValuesType } from './types';\n\ndescribe('main function', () => {\n  const baseOptions: Omit<InitialValuesType, 'csvToInsert'> = {\n    commentCharacter: '#',\n    separator: ',',\n    quoteChar: '\"',\n    insertingPosition: 'append',\n    customFill: false,\n    customFillValue: '',\n    customPostionOptions: 'headerName',\n    headerName: 'age',\n    rowNumber: 1\n  };\n\n  const originalCsv = `name,age\\nAlice,30\\nBob,25`;\n\n  it('should return empty string if input or csvToInsert is empty', () => {\n    expect(main('', { ...baseOptions, csvToInsert: '' })).toBe('');\n    expect(main(originalCsv, { ...baseOptions, csvToInsert: '' })).toBe('');\n  });\n\n  it('should append columns at the end', () => {\n    const csvToInsert = `email\\nalice@mail.com\\nbob@mail.com`;\n    const result = main(originalCsv, {\n      ...baseOptions,\n      insertingPosition: 'append',\n      csvToInsert\n    });\n    expect(result).toBe(\n      'name,age,email\\nAlice,30,alice@mail.com\\nBob,25,bob@mail.com'\n    );\n  });\n\n  it('should prepend columns at the beginning', () => {\n    const csvToInsert = `email\\nalice@mail.com\\nbob@mail.com`;\n    const result = main(originalCsv, {\n      ...baseOptions,\n      insertingPosition: 'prepend',\n      csvToInsert\n    });\n    expect(result).toBe(\n      'email,name,age\\nalice@mail.com,Alice,30\\nbob@mail.com,Bob,25'\n    );\n  });\n\n  it('should insert columns after a header name', () => {\n    const csvToInsert = `email\\nalice@mail.com\\nbob@mail.com`;\n    const result = main(originalCsv, {\n      ...baseOptions,\n      insertingPosition: 'custom',\n      customPostionOptions: 'headerName',\n      headerName: 'name',\n      csvToInsert\n    });\n    expect(result).toBe(\n      'name,email,age\\nAlice,alice@mail.com,30\\nBob,bob@mail.com,25'\n    );\n  });\n\n  it('should insert columns after a row number (column index)', () => {\n    const csvToInsert = `email\\nalice@mail.com\\nbob@mail.com`;\n    const result = main(originalCsv, {\n      ...baseOptions,\n      insertingPosition: 'custom',\n      customPostionOptions: 'rowNumber',\n      rowNumber: 0,\n      csvToInsert\n    });\n    expect(result).toBe(\n      'email,name,age\\nalice@mail.com,Alice,30\\nbob@mail.com,Bob,25'\n    );\n  });\n\n  it('should handle missing values and fill with empty string by default', () => {\n    const csv = `name\\nAlice\\nBob`;\n    const csvToInsert = `email\\nalice@mail.com\\n`; // second row is missing\n    const result = main(csv, {\n      ...baseOptions,\n      insertingPosition: 'append',\n      csvToInsert\n    });\n    expect(result).toBe('name,email\\nAlice,alice@mail.com\\nBob,');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/csv/insert-csv-columns/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('csv', {\n  i18n: {\n    name: 'csv:insertCsvColumns.title',\n    description: 'csv:insertCsvColumns.description',\n    shortDescription: 'csv:insertCsvColumns.shortDescription'\n  },\n\n  path: 'insert-csv-columns',\n  icon: 'hugeicons:column-insert',\n\n  keywords: ['insert', 'csv', 'columns', 'append', 'prepend'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/csv/insert-csv-columns/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { splitCsv } from '@utils/csv';\nimport { transpose, normalizeAndFill } from '@utils/array';\n\nexport function main(input: string, options: InitialValuesType): string {\n  if (!input || !options.csvToInsert) return '';\n\n  // Parse input CSV and insert CSV\n  const inputRows = splitCsv(\n    input,\n    true,\n    options.commentCharacter,\n    true,\n    options.separator,\n    options.quoteChar\n  );\n\n  const filledRows = options.customFill\n    ? normalizeAndFill(inputRows, options.customFillValue)\n    : normalizeAndFill(inputRows, '');\n\n  let columns = transpose(filledRows);\n\n  const csvToInsertRows = splitCsv(\n    options.csvToInsert,\n    true,\n    options.commentCharacter,\n    true,\n    options.separator,\n    options.quoteChar\n  );\n\n  const filledCsvToInsertRows = options.customFill\n    ? normalizeAndFill(csvToInsertRows, options.customFillValue)\n    : normalizeAndFill(csvToInsertRows, '');\n\n  const columnsToInsert = transpose(filledCsvToInsertRows);\n\n  switch (options.insertingPosition) {\n    case 'prepend':\n      columns = [...columnsToInsert, ...columns];\n      break;\n\n    case 'append':\n      columns = [...columns, ...columnsToInsert];\n      break;\n\n    case 'custom':\n      if (options.customPostionOptions === 'headerName') {\n        const headerName = options.headerName;\n        const index = filledRows[0].indexOf(headerName);\n        if (index !== -1) {\n          columns = [\n            ...columns.slice(0, index + 1),\n            ...columnsToInsert,\n            ...columns.slice(index + 1)\n          ];\n        } // else: keep original columns\n      } else if (options.customPostionOptions === 'rowNumber') {\n        const index = options.rowNumber;\n        if (index >= 0 && index <= columns.length) {\n          columns = [\n            ...columns.slice(0, index),\n            ...columnsToInsert,\n            ...columns.slice(index)\n          ];\n        } // else: keep original columns\n      }\n      break;\n\n    default:\n      // no-op, keep original columns\n      break;\n  }\n\n  // Transpose back to rows\n  const normalizedColumns = normalizeAndFill(columns, options.customFillValue);\n  const finalRows = transpose(normalizedColumns);\n\n  return finalRows.map((row) => row.join(options.separator)).join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/csv/insert-csv-columns/types.ts",
    "content": "export type insertingPosition = 'prepend' | 'append' | 'custom';\nexport type customPostion = 'headerName' | 'rowNumber';\n\nexport type InitialValuesType = {\n  csvToInsert: string;\n  separator: string;\n  quoteChar: string;\n  commentCharacter: string;\n  customFill: boolean;\n  customFillValue: string;\n  insertingPosition: insertingPosition;\n  customPostionOptions: customPostion;\n  headerName: string;\n  rowNumber: number;\n};\n"
  },
  {
    "path": "src/pages/tools/csv/swap-csv-columns/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Box } from '@mui/material';\nimport ToolContent from '@components/ToolContent';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { csvColumnsSwap } from './service';\nimport { getCsvHeaders } from '@utils/csv';\nimport { InitialValuesType } from './types';\n\nconst initialValues: InitialValuesType = {\n  fromPositionStatus: true,\n  toPositionStatus: true,\n  fromPosition: '1',\n  toPosition: '2',\n  fromHeader: '',\n  toHeader: '',\n  emptyValuesFilling: true,\n  customFiller: '',\n  deleteComment: true,\n  commentCharacter: '#',\n  emptyLines: true\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Move the Key Column to the First Position',\n    description:\n      'In this example, we use our CSV column swapping tool to bring the most important information to the first column. As we are planning to go on vacation soon, in the input of the tool, we load data about national parks that include their names and locations. To decide, which is the closest park to us, we need to see the parks location first, therefore, we swap the first and second data columns so that the \"location\" column is at the beginning of the CSV data.',\n    sampleText: `park_name,location\nYellowstone,Wyoming\nYosemite,California\nGrand Canyon,Arizona\nRocky Mountain,Colorado\nZion Park,Utah`,\n    sampleResult: `location,park_name\nWyoming,Yellowstone\nCalifornia,Yosemite\nArizona,Grand Canyon\nColorado,Rocky Mountain\nUtah,Zion Park`,\n    sampleOptions: {\n      fromPositionStatus: true,\n      toPositionStatus: true,\n      fromPosition: '1',\n      toPosition: '2',\n      fromHeader: 'park_name',\n      toHeader: 'location',\n      emptyValuesFilling: false,\n      customFiller: '*',\n      deleteComment: false,\n      commentCharacter: '',\n      emptyLines: false\n    }\n  },\n  {\n    title: 'Reorganize Columns in Vitamins CSV',\n    description:\n      'In this example, a lab intern made a mistake and created a corrupted CSV file with mixed-up columns and missing data. To fix the file, we swap the columns based on the headers \"Vitamin\" and \"Function\" so that the \"Vitamin\" column becomes the first in the output data. We also fill the incomplete CSV data by adding a custom asterisk \"*\" symbol in place of missing values.',\n    sampleText: `Function,Fat-Soluble,Vitamin,Sources\nSupports vision,Fat-Soluble,A,Carrots\nImmune function,Water-Soluble,C,Citrus fruits\nBone health,Fat-Soluble,D,Fatty fish\nAntioxidant,Fat-Soluble,E,Nuts\nBlood clotting,Fat-Soluble,K,Leafy greens\nEnergy production,Water-Soluble,B1\nEnergy production,Water-Soluble,B2\nEnergy production,Water-Soluble,B3,Meat\nProtein metabolism,Water-Soluble,B6,Poultry\nNervous system,Water-Soluble,B12,Meat`,\n    sampleResult: `Vitamin,Fat-Soluble,Function,Sources\nA,Fat-Soluble,Supports vision,Carrots\nC,Water-Soluble,Immune function,Citrus fruits\nD,Fat-Soluble,Bone health,Fatty fish\nE,Fat-Soluble,Antioxidant,Nuts\nK,Fat-Soluble,Blood clotting,Leafy greens\nB1,Water-Soluble,Energy production,*\nB2,Water-Soluble,Energy production,*\nB3,Water-Soluble,Energy production,Meat\nB6,Water-Soluble,Protein metabolism,Poultry\nB12,Water-Soluble,Nervous system,Meat`,\n    sampleOptions: {\n      fromPositionStatus: false,\n      toPositionStatus: false,\n      fromPosition: '1',\n      toPosition: '2',\n      fromHeader: 'Vitamin',\n      toHeader: 'Function',\n      emptyValuesFilling: false,\n      customFiller: '*',\n      deleteComment: false,\n      commentCharacter: '',\n      emptyLines: false\n    }\n  },\n  {\n    title: 'Place Columns Side by Side for Analysis',\n    description:\n      'In this example, we change the order of columns in a CSV dataset to have the columns essential for analysis adjacent to each other. We match the \"ScreenSize\" column by its name and place it in the second-to-last position \"-2\". This groups the \"ScreenSize\" and \"Price\" columns together, allowing us to easily compare and choose the phone we want to buy. We also remove empty lines and specify that lines starting with the \"#\" symbol are comments and should be left as is.',\n    sampleText: `Brand,Model,ScreenSize,OS,Price\n\nApple,iPhone 15 Pro Max,6.7″,iOS,$1299\nSamsung,Galaxy S23 Ultra,6.8″,Android,$1199\nGoogle,Pixel 7 Pro,6.4″,Android,$899\n\n#OnePlus,11 Pro,6.7″,Android,$949\nXiaomi,13 Ultra,6.6″,Android,$849`,\n    sampleResult: `Brand,Model,OS,ScreenSize,Price\nApple,iPhone 15 Pro Max,iOS,6.7″,$1299\nSamsung,Galaxy S23 Ultra,Android,6.8″,$1199\nGoogle,Pixel 7 Pro,Android,6.4″,$899\nXiaomi,13 Ultra,Android,6.6″,$849`,\n    sampleOptions: {\n      fromPositionStatus: false,\n      toPositionStatus: true,\n      fromPosition: '1',\n      toPosition: '4',\n      fromHeader: 'ScreenSize',\n      toHeader: 'OS',\n      emptyValuesFilling: true,\n      customFiller: 'x',\n      deleteComment: true,\n      commentCharacter: '#',\n      emptyLines: true\n    }\n  }\n];\n\nexport default function CsvToTsv({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: string) => {\n    setResult(csvColumnsSwap(input, optionsValues));\n  };\n\n  const headers = getCsvHeaders(input);\n  const headerOptions = headers.map((item) => ({\n    label: `${item}`,\n    value: item\n  }));\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Swap-From Column',\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('fromPositionStatus', true)}\n            title=\"Set Column-From position\"\n            checked={values.fromPositionStatus}\n          />\n          {values.fromPositionStatus && (\n            <TextFieldWithDesc\n              description={'Position of the first column you want to swap'}\n              value={values.fromPosition}\n              onOwnChange={(val) => updateField('fromPosition', val)}\n              type=\"number\"\n              inputProps={{ min: 1, max: headers.length }}\n            />\n          )}\n\n          <SimpleRadio\n            onClick={() => updateField('fromPositionStatus', false)}\n            title=\"Set Column-From Header\"\n            checked={!values.fromPositionStatus}\n          />\n          {!values.fromPositionStatus && (\n            <SelectWithDesc\n              selected={values.fromHeader}\n              options={headerOptions}\n              onChange={(value) => updateField('fromHeader', value)}\n              description={'Header of the first column you want to swap.'}\n            />\n          )}\n        </Box>\n      )\n    },\n    {\n      title: 'Swap-to Column',\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('toPositionStatus', true)}\n            title=\"Set Column-To position\"\n            checked={values.toPositionStatus}\n          />\n          {values.toPositionStatus && (\n            <TextFieldWithDesc\n              description={'Position of the second column you want to swap'}\n              value={values.toPosition}\n              onOwnChange={(val) => updateField('toPosition', val)}\n              type=\"number\"\n              inputProps={{ min: 1, max: headers.length }}\n            />\n          )}\n          <SimpleRadio\n            onClick={() => updateField('toPositionStatus', false)}\n            title=\"Set Column-To Header\"\n            checked={!values.toPositionStatus}\n          />\n          {!values.toPositionStatus && (\n            <SelectWithDesc\n              selected={values.toHeader}\n              options={headerOptions}\n              onChange={(value) => updateField('toHeader', value)}\n              description={'Header of the second column you want to swap..'}\n            />\n          )}\n        </Box>\n      )\n    },\n    {\n      title: 'Incomplete Data',\n      component: (\n        <Box>\n          <SelectWithDesc\n            selected={values.emptyValuesFilling}\n            options={[\n              { label: 'Fill With Empty Values', value: true },\n              { label: 'Fill with Custom Values', value: false }\n            ]}\n            onChange={(value) => updateField('emptyValuesFilling', value)}\n            description={\n              'Fill incomplete CSV data with empty symbols or a custom symbol.'\n            }\n          />\n          {!values.emptyValuesFilling && (\n            <TextFieldWithDesc\n              description={\n                'Specify a custom symbol to fill incomplete CSV data with'\n              }\n              value={values.customFiller}\n              onOwnChange={(val) => updateField('customFiller', val)}\n            />\n          )}\n        </Box>\n      )\n    },\n    {\n      title: 'Comments and Empty Lines',\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            checked={values.deleteComment}\n            onChange={(value) => updateField('deleteComment', value)}\n            title=\"Delete Comments\"\n            description=\"if checked, comments given by the following character will be deleted\"\n          />\n          {values.deleteComment && (\n            <TextFieldWithDesc\n              description={\n                'Specify the character used to start comments in the input CSV (and if needed remove them via checkbox above)'\n              }\n              value={values.commentCharacter}\n              onOwnChange={(val) => updateField('commentCharacter', val)}\n            />\n          )}\n\n          <CheckboxWithDesc\n            checked={values.emptyLines}\n            onChange={(value) => updateField('emptyLines', value)}\n            title=\"Delete Empty Lines\"\n            description=\"Do not include empty lines in the output data.\"\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/csv/swap-csv-columns/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('csv', {\n  i18n: {\n    name: 'csv:swapCsvColumns.title',\n    description: 'csv:swapCsvColumns.description',\n    shortDescription: 'csv:swapCsvColumns.shortDescription',\n    longDescription: 'csv:swapCsvColumns.longDescription'\n  },\n\n  path: 'swap-csv-columns',\n  icon: 'eva:swap-outline',\n  keywords: ['csv', 'swap', 'columns'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/csv/swap-csv-columns/service.ts",
    "content": "import { splitCsv } from '@utils/csv';\nimport { InitialValuesType } from './types';\n\nfunction retrieveFromAndTo(\n  headerRow: string[],\n  options: InitialValuesType\n): number[] {\n  const from = options.fromPositionStatus\n    ? Number(options.fromPosition)\n    : headerRow.findIndex((header) => header === options.fromHeader) + 1;\n\n  const to = options.toPositionStatus\n    ? Number(options.toPosition)\n    : headerRow.findIndex((header) => header === options.toHeader) + 1;\n\n  if (from <= 0 || to <= 0)\n    throw new Error('Invalid column positions. Check headers or positions.');\n\n  if (from > headerRow.length || to > headerRow.length)\n    throw new Error(`There are only ${headerRow.length} columns`);\n\n  return [from, to];\n}\n\nfunction swap(lines: string[][], from: number, to: number): string[][] {\n  if (from <= 0 || to <= 0)\n    throw new Error('Columns position must be greater than zero ');\n\n  return lines.map((row) => {\n    const newRow = [...row]; // Clone the row to avoid mutating the original\n    [newRow[from - 1], newRow[to - 1]] = [newRow[to - 1], newRow[from - 1]]; // Swap values\n    return newRow;\n  });\n}\n\nexport function csvColumnsSwap(input: string, options: InitialValuesType) {\n  if (!input) {\n    return '';\n  }\n  // split csv input and remove comments\n  const rows = splitCsv(\n    input,\n    options.deleteComment,\n    options.commentCharacter,\n    options.emptyLines\n  );\n\n  const columnCount = Math.max(...rows.map((row) => row.length));\n  for (let i = 0; i < rows.length; i++) {\n    for (let j = 0; j < columnCount; j++) {\n      if (!rows[i][j]) {\n        rows[i][j] = options.emptyValuesFilling ? '' : options.customFiller;\n      }\n    }\n  }\n\n  const positions = retrieveFromAndTo(rows[0], options);\n\n  const result = swap(rows, positions[0], positions[1]);\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/csv/swap-csv-columns/swap-csv-columns.service.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { InitialValuesType } from './types';\nimport { csvColumnsSwap } from './service';\n\ndescribe('csvColumnsSwap', () => {\n  it('should swap columns by position', () => {\n    const input = 'A,B,C\\n1,2,3\\n4,5,6';\n\n    const options: InitialValuesType = {\n      fromPositionStatus: true, // fromPositionStatus\n      fromPosition: '1', // fromPosition\n      toPositionStatus: true, // toPositionStatus\n      toPosition: '3', // toPosition\n      fromHeader: '', // fromHeader\n      toHeader: '', // toHeader\n      emptyValuesFilling: true, // dataCompletion\n      customFiller: '', // customFiller\n      deleteComment: false, // deleteComment\n      commentCharacter: '#', // commentCharacter\n      emptyLines: true // emptyLines\n    };\n\n    const result = csvColumnsSwap(input, options);\n    expect(result).toBe('C,B,A\\n3,2,1\\n6,5,4');\n  });\n\n  it('should swap columns by header', () => {\n    const input = 'A,B,C\\n1,2,3\\n4,5,6';\n    const options: InitialValuesType = {\n      fromPositionStatus: false, // fromPositionStatus\n      fromPosition: '', // fromPosition\n      toPositionStatus: false, // toPositionStatus\n      toPosition: '', // toPosition\n      fromHeader: 'A', // fromHeader\n      toHeader: 'C', // toHeader\n      emptyValuesFilling: true, // dataCompletion\n      customFiller: '', // customFiller\n      deleteComment: false, // deleteComment\n      commentCharacter: '#', // commentCharacter\n      emptyLines: true // emptyLines\n    };\n    const result = csvColumnsSwap(input, options);\n    expect(result).toBe('C,B,A\\n3,2,1\\n6,5,4');\n  });\n\n  it('should fill missing values with custom filler', () => {\n    const input = 'A,B,C\\n1,2\\n4';\n    const options: InitialValuesType = {\n      fromPositionStatus: true, // fromPositionStatus\n      fromPosition: '1', // fromPosition\n      toPositionStatus: true, // toPositionStatus\n      toPosition: '3', // toPosition\n      fromHeader: '', // fromHeader\n      toHeader: '', // toHeader\n      emptyValuesFilling: false, // dataCompletion\n      customFiller: 'X', // customFiller\n      deleteComment: false, // deleteComment\n      commentCharacter: '#', // commentCharacter\n      emptyLines: true // emptyLines\n    };\n    const result = csvColumnsSwap(input, options);\n    expect(result).toBe('C,B,A\\nX,2,1\\nX,X,4');\n  });\n\n  it('should skip filling missing values', () => {\n    const input = 'A,B,C\\n1,2\\n4';\n    const options: InitialValuesType = {\n      fromPositionStatus: true, // fromPositionStatus\n      fromPosition: '1', // fromPosition\n      toPositionStatus: true, // toPositionStatus\n      toPosition: '3', // toPosition\n      fromHeader: '', // fromHeader\n      toHeader: '', // toHeader\n      emptyValuesFilling: true, // dataCompletion\n      customFiller: '', // customFiller\n      deleteComment: false, // deleteComment\n      commentCharacter: '#', // commentCharacter\n      emptyLines: true // emptyLines\n    };\n    const result = csvColumnsSwap(input, options);\n    expect(result).toBe('C,B,A\\n,2,1\\n,,4');\n  });\n\n  it('should throw an error for invalid column positions', () => {\n    const input = 'A,B,C\\n1,2,3\\n4,5,6';\n    const options: InitialValuesType = {\n      fromPositionStatus: true, // fromPositionStatus\n      fromPosition: '0', // fromPosition\n      toPositionStatus: true, // toPositionStatus\n      toPosition: '3', // toPosition\n      fromHeader: '', // fromHeader\n      toHeader: '', // toHeader\n      emptyValuesFilling: true, // dataCompletion\n      customFiller: '', // customFiller\n      deleteComment: false, // deleteComment\n      commentCharacter: '#', // commentCharacter\n      emptyLines: true // emptyLines\n    };\n    expect(() => csvColumnsSwap(input, options)).toThrow(\n      'Invalid column positions. Check headers or positions.'\n    );\n  });\n\n  it('should handle empty input gracefully', () => {\n    const input = '';\n    const options: InitialValuesType = {\n      fromPositionStatus: true, // fromPositionStatus\n      fromPosition: '1', // fromPosition\n      toPositionStatus: true, // toPositionStatus\n      toPosition: '3', // toPosition\n      fromHeader: '', // fromHeader\n      toHeader: '', // toHeader\n      emptyValuesFilling: true, // dataCompletion\n      customFiller: '', // customFiller\n      deleteComment: false, // deleteComment\n      commentCharacter: '#', // commentCharacter\n      emptyLines: true // emptyLines\n    };\n    const result = csvColumnsSwap(input, options);\n    expect(result).toBe('');\n  });\n\n  it('should remove comments if deleteComment is true', () => {\n    const input = '# Comment\\nA,B,C\\n1,2,3\\n4,5,6';\n    const options: InitialValuesType = {\n      fromPositionStatus: true, // fromPositionStatus\n      fromPosition: '1', // fromPosition\n      toPositionStatus: true, // toPositionStatus\n      toPosition: '3', // toPosition\n      fromHeader: '', // fromHeader\n      toHeader: '', // toHeader\n      emptyValuesFilling: true, // dataCompletion\n      customFiller: '', // customFiller\n      deleteComment: true, // deleteComment\n      commentCharacter: '#', // commentCharacter\n      emptyLines: true // emptyLines\n    };\n    const result = csvColumnsSwap(input, options);\n    expect(result).toBe('C,B,A\\n3,2,1\\n6,5,4');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/csv/swap-csv-columns/types.ts",
    "content": "export type InitialValuesType = {\n  fromPositionStatus: boolean;\n  fromPosition: string | '';\n  toPositionStatus: boolean;\n  toPosition: string | '';\n  fromHeader: string | '';\n  toHeader: string | '';\n  emptyValuesFilling: boolean;\n  customFiller: string | '';\n  deleteComment: boolean;\n  commentCharacter: string | '';\n  emptyLines: boolean;\n};\n"
  },
  {
    "path": "src/pages/tools/csv/transpose-csv/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { transposeCSV } from './service';\nimport { InitialValuesType } from './types';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\n\nconst initialValues: InitialValuesType = {\n  separator: ',',\n  commentCharacter: '#',\n  customFill: false,\n  customFillValue: 'x',\n  quoteChar: '\"'\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Transpose a 2x3 CSV',\n    description:\n      'This example transposes a CSV with 2 rows and 3 columns. The tool splits the input data by the comma character, creating a 2 by 3 matrix. It then exchanges elements, turning columns into rows and vice versa. The output is a transposed CSV with flipped dimensions',\n    sampleText: `foo,bar,baz\nval1,val2,val3`,\n    sampleResult: `foo,val1\nbar,val2\nbaz,val3`,\n    sampleOptions: {\n      separator: ',',\n      commentCharacter: '#',\n      customFill: false,\n      customFillValue: 'x',\n      quoteChar: '\"'\n    }\n  },\n  {\n    title: 'Transpose a Long CSV',\n    description:\n      'In this example, we flip a vertical single-column CSV file containing a list of our favorite fruits and their emojis. This single column is transformed into a single-row CSV file and the rows length matches the height of the original CSV.',\n    sampleText: `Tasty Fruit\n🍑 peaches\n🍒 cherries\n🥝 kiwis\n🍓 strawberries\n🍎 apples\n🍐 pears\n🥭 mangos\n🍍 pineapples\n🍌 bananas\n🍊 tangerines\n🍉 watermelons\n🍇 grapes`,\n    sampleResult: `fTasty Fruit,🍑 peaches,🍒 cherries,🥝 kiwis,🍓 strawberries,🍎 apples,🍐 pears,🥭 mangos,🍍 pineapples,🍌 bananas,🍊 tangerines,🍉 watermelons,🍇 grapes`,\n    sampleOptions: {\n      separator: ',',\n      commentCharacter: '#',\n      customFill: false,\n      customFillValue: 'x',\n      quoteChar: '\"'\n    }\n  },\n  {\n    title: 'Clean and Transpose CSV Data',\n    description:\n      'In this example, we perform three tasks simultaneously: transpose a CSV file, remove comments and empty lines, and fix missing data. The transposition operation is the same as flipping a matrix across its diagonal and it is done automatically by the program. Additionally, the program automatically removes all empty lines as they cannot be transposed. The comments are removed by specifying the \"#\" symbol in the options. The program also fixes missing data using a custom bullet symbol \"•\", which is specified in the options.',\n    sampleText: `Fish Type,Color,Habitat\nGoldfish,Gold,Freshwater\n\n#Clownfish,Orange,Coral Reefs\nTuna,Silver,Saltwater\n\nShark,Grey,Saltwater\nSalmon,Silver`,\n    sampleResult: `Fish Type,Goldfish,Tuna,Shark,Salmon\nColor,Gold,Silver,Grey,Silver\nHabitat,Freshwater,Saltwater,Saltwater,•`,\n    sampleOptions: {\n      separator: ',',\n      commentCharacter: '#',\n      customFill: true,\n      customFillValue: '•',\n      quoteChar: '\"'\n    }\n  }\n];\nexport default function TransposeCsv({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    setResult(transposeCSV(input, values));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Csv input Options',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.separator}\n            onOwnChange={(val) => updateField('separator', val)}\n            description={\n              'Enter the character used to delimit columns in the CSV input file.'\n            }\n          />\n          <TextFieldWithDesc\n            value={values.quoteChar}\n            onOwnChange={(val) => updateField('quoteChar', val)}\n            description={\n              'Enter the quote character used to quote the CSV input fields.'\n            }\n          />\n          <TextFieldWithDesc\n            value={values.commentCharacter}\n            onOwnChange={(val) => updateField('commentCharacter', val)}\n            description={\n              'Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.'\n            }\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Fixing CSV Options',\n      component: (\n        <Box>\n          <SelectWithDesc\n            selected={values.customFill}\n            options={[\n              { label: 'Fill With Empty Values', value: false },\n              { label: 'Fill with Custom Values', value: true }\n            ]}\n            onChange={(value) => updateField('customFill', value)}\n            description={\n              'Insert empty fields or custom values where the CSV data is missing (not empty).'\n            }\n          />\n\n          {values.customFill && (\n            <TextFieldWithDesc\n              value={values.customFillValue}\n              onOwnChange={(val) => updateField('customFillValue', val)}\n              description={\n                'Enter the character used to fill missing values in the CSV input file.'\n              }\n            />\n          )}\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput title=\"Input CSV\" value={input} onChange={setInput} />\n      }\n      resultComponent={<ToolTextResult title=\"Transposed CSV\" value={result} />}\n      initialValues={initialValues}\n      exampleCards={exampleCards}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/csv/transpose-csv/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('csv', {\n  i18n: {\n    name: 'csv:transposeCsv.title',\n    description: 'csv:transposeCsv.description',\n    shortDescription: 'csv:transposeCsv.shortDescription',\n    longDescription: 'csv:transposeCsv.longDescription'\n  },\n\n  path: 'transpose-csv',\n  icon: 'carbon:transpose',\n\n  keywords: ['transpose', 'csv'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/csv/transpose-csv/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { transpose, normalizeAndFill } from '@utils/array';\nimport { splitCsv } from '@utils/csv';\n\nexport function transposeCSV(\n  input: string,\n  options: InitialValuesType\n): string {\n  if (!input) return '';\n\n  const rows = splitCsv(\n    input,\n    true,\n    options.commentCharacter,\n    true,\n    options.separator,\n    options.quoteChar\n  );\n\n  const normalizeAndFillRows = options.customFill\n    ? normalizeAndFill(rows, options.customFillValue)\n    : normalizeAndFill(rows, '');\n\n  return transpose(normalizeAndFillRows)\n    .map((row) => row.join(options.separator))\n    .join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/csv/transpose-csv/transpose-csv.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { transposeCSV } from './service';\nimport { InitialValuesType } from './types';\n\ndescribe('transposeCsv', () => {\n  it('should transpose a simple CSV', () => {\n    const options: InitialValuesType = {\n      separator: ',',\n      commentCharacter: '#',\n      customFill: false,\n      customFillValue: 'x',\n      quoteChar: '\"'\n    };\n    const input = 'a,b,c\\n1,2,3';\n    const expectedOutput = 'a,1\\nb,2\\nc,3';\n    const result = transposeCSV(input, options);\n    expect(result).toEqual(expectedOutput);\n  });\n\n  it('should handle an empty CSV', () => {\n    const options: InitialValuesType = {\n      separator: ',',\n      commentCharacter: '#',\n      customFill: false,\n      customFillValue: 'x',\n      quoteChar: '\"'\n    };\n    const input = '';\n    const expectedOutput = '';\n    const result = transposeCSV(input, options);\n    expect(result).toEqual(expectedOutput);\n  });\n\n  it('should handle a single row CSV', () => {\n    const options: InitialValuesType = {\n      separator: ',',\n      commentCharacter: '#',\n      customFill: false,\n      customFillValue: 'x',\n      quoteChar: '\"'\n    };\n    const input = 'a,b,c';\n    const expectedOutput = 'a\\nb\\nc';\n    const result = transposeCSV(input, options);\n    expect(result).toEqual(expectedOutput);\n  });\n\n  it('should handle a single column CSV', () => {\n    const options: InitialValuesType = {\n      separator: ',',\n      commentCharacter: '#',\n      customFill: false,\n      customFillValue: 'x',\n      quoteChar: '\"'\n    };\n    const input = 'a\\nb\\nc';\n    const expectedOutput = 'a,b,c';\n    const result = transposeCSV(input, options);\n    expect(result).toEqual(expectedOutput);\n  });\n\n  it('should handle uneven rows in the CSV', () => {\n    const options: InitialValuesType = {\n      separator: ',',\n      commentCharacter: '#',\n      customFill: true,\n      customFillValue: 'x',\n      quoteChar: '\"'\n    };\n    const input = 'a,b\\n1,2,3';\n    const expectedOutput = 'a,1\\nb,2\\nx,3';\n    const result = transposeCSV(input, options);\n    expect(result).toEqual(expectedOutput);\n  });\n\n  it('should skip comment in  the CSV', () => {\n    const options: InitialValuesType = {\n      separator: ',',\n      commentCharacter: '#',\n      customFill: true,\n      customFillValue: 'x',\n      quoteChar: '\"'\n    };\n    const input = 'a,b\\n1,2\\n#c,3\\nd,4';\n    const expectedOutput = 'a,1,d\\nb,2,4';\n    const result = transposeCSV(input, options);\n    expect(result).toEqual(expectedOutput);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/csv/transpose-csv/types.ts",
    "content": "export type InitialValuesType = {\n  separator: string;\n  commentCharacter: string;\n  customFill: boolean;\n  customFillValue: string;\n  quoteChar: string;\n};\n"
  },
  {
    "path": "src/pages/tools/csv/tsv-to-json/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolCodeInput from '@components/input/ToolCodeInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { convertTsvToJson } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { Box } from '@mui/material';\nimport { updateNumberField } from '@utils/string';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { InitialValuesType } from './types';\n\nconst initialValues: InitialValuesType = {\n  delimiter: '\\t',\n  quote: '\"',\n  comment: '#',\n  useHeaders: true,\n  skipEmptyLines: true,\n  dynamicTypes: true,\n  indentationType: 'space',\n  spacesCount: 2\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Basic TSV to JSON Array',\n    description:\n      'Convert a simple TSV file into a JSON array structure by using spaces as formatting indentation.',\n    sampleText: `name\tage\tcity\nJohn\t30\tNew York\nAlice\t25\tLondon`,\n    sampleResult: `[\n  {\n    \"name\": \"John\",\n    \"age\": 30,\n    \"city\": \"New York\"\n  },\n  {\n    \"name\": \"Alice\",\n    \"age\": 25,\n    \"city\": \"London\"\n  }\n]`,\n    sampleOptions: {\n      ...initialValues,\n      useHeaders: true,\n      dynamicTypes: true\n    }\n  },\n  {\n    title: 'Turn TSV to JSON without Headers',\n    description: 'Convert a TSV file in minified JSON file.',\n    sampleText: `Square\tTriangle\tCircle\nCube\tCone\tSphere\n#Oval`,\n    sampleResult: `[[\"Square\",\"Triangle\",\"Circle\"],[\"Cube\",\"Cone\",\"Sphere\"]]`,\n    sampleOptions: {\n      ...initialValues,\n      useHeaders: false,\n      indentationType: 'none'\n    }\n  },\n  {\n    title: 'Transform TSV to JSON with Headers',\n    description: 'Convert a TSV file with headers into a JSON file.',\n    sampleText: `item\tmaterial\tquantity\n\n\nHat\tWool\t3\nGloves\tLeather\t5\nCandle\tWax\t4\nVase\tGlass\t2\n\nSculpture\tBronze\t1\nTable\tWood\t1\n\nBookshelf\tWood\t2`,\n    sampleResult: `[\n  {\n    \"item\": \"Hat\",\n    \"material\": \"Wool\",\n    \"quantity\": 3\n  },\n  {\n    \"item\": \"Gloves\",\n    \"material\": \"Leather\",\n    \"quantity\": 5\n  },\n  {\n    \"item\": \"Candle\",\n    \"material\": \"Wax\",\n    \"quantity\": 4\n  },\n  {\n    \"item\": \"Vase\",\n    \"material\": \"Glass\",\n    \"quantity\": 2\n  },\n  {\n    \"item\": \"Sculpture\",\n    \"material\": \"Bronze\",\n    \"quantity\": 1\n  },\n  {\n    \"item\": \"Table\",\n    \"material\": \"Wood\",\n    \"quantity\": 1\n  },\n  {\n    \"item\": \"Bookshelf\",\n    \"material\": \"Wood\",\n    \"quantity\": 2\n  }\n]`,\n    sampleOptions: {\n      ...initialValues\n    }\n  }\n];\n\nexport default function TsvToJson({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    setResult(convertTsvToJson(input, values));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Input CSV Format',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            description=\"Character used to qutoe tsv values\"\n            onOwnChange={(val) => updateField('quote', val)}\n            value={values.quote}\n          />\n          <TextFieldWithDesc\n            description=\"Symbol use to mark comments in the TSV\"\n            value={values.comment}\n            onOwnChange={(val) => updateField('comment', val)}\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Conversion Options',\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            checked={values.useHeaders}\n            onChange={(value) => updateField('useHeaders', value)}\n            title=\"Use Headers\"\n            description=\"First row is treated as column headers\"\n          />\n          <CheckboxWithDesc\n            checked={values.dynamicTypes}\n            onChange={(value) => updateField('dynamicTypes', value)}\n            title=\"Dynamic Types\"\n            description=\"Convert numbers and booleans to their proper types\"\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Output Formatting',\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('indentationType', 'space')}\n            checked={values.indentationType === 'space'}\n            title={'Use Spaces for indentation'}\n          />\n          {values.indentationType === 'space' && (\n            <TextFieldWithDesc\n              description=\"Number of spaces for indentation\"\n              value={values.spacesCount}\n              onOwnChange={(val) =>\n                updateNumberField(val, 'spacesCount', updateField)\n              }\n              type=\"number\"\n            />\n          )}\n          <SimpleRadio\n            onClick={() => updateField('indentationType', 'tab')}\n            checked={values.indentationType === 'tab'}\n            title={'Use Tabs for indentation'}\n          />\n          <SimpleRadio\n            onClick={() => updateField('indentationType', 'none')}\n            checked={values.indentationType === 'none'}\n            title={'Minify JSON'}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={initialValues}\n      compute={compute}\n      exampleCards={exampleCards}\n      getGroups={getGroups}\n      inputComponent={\n        <ToolCodeInput\n          title=\"Input TSV\"\n          value={input}\n          onChange={setInput}\n          language=\"tsv\"\n        />\n      }\n      resultComponent={\n        <ToolTextResult title=\"Output JSON\" value={result} extension={'json'} />\n      }\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/csv/tsv-to-json/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('csv', {\n  path: 'tsv-to-json',\n  icon: 'material-symbols:code',\n\n  keywords: ['tsv', 'json', 'convert', 'tabular'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'csv:tsvToJson.title',\n    description: 'csv:tsvToJson.description',\n    shortDescription: 'csv:tsvToJson.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/csv/tsv-to-json/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { beautifyJson } from '../../json/prettify/service';\nimport { minifyJson } from '../../json/minify/service';\n\nexport function convertTsvToJson(\n  input: string,\n  options: InitialValuesType\n): string {\n  if (!input) return '';\n  const lines = input.split('\\n');\n  const result: any[] = [];\n  let headers: string[] = [];\n\n  // Filter out comments and empty lines\n  const validLines = lines.filter((line) => {\n    const trimmedLine = line.trim();\n    return (\n      trimmedLine &&\n      (!options.skipEmptyLines ||\n        !containsOnlyCustomCharAndSpaces(trimmedLine, options.delimiter)) &&\n      !trimmedLine.startsWith(options.comment)\n    );\n  });\n\n  if (validLines.length === 0) {\n    return '[]';\n  }\n\n  // Parse headers if enabled\n  if (options.useHeaders) {\n    headers = parseCsvLine(validLines[0], options);\n    validLines.shift();\n  }\n\n  // Parse data lines\n  for (const line of validLines) {\n    const values = parseCsvLine(line, options);\n\n    if (options.useHeaders) {\n      const obj: Record<string, any> = {};\n      headers.forEach((header, i) => {\n        obj[header] = parseValue(values[i], options.dynamicTypes);\n      });\n      result.push(obj);\n    } else {\n      result.push(values.map((v) => parseValue(v, options.dynamicTypes)));\n    }\n  }\n\n  return options.indentationType === 'none'\n    ? minifyJson(JSON.stringify(result))\n    : beautifyJson(\n        JSON.stringify(result),\n        options.indentationType,\n        options.spacesCount\n      );\n}\n\nconst parseCsvLine = (line: string, options: InitialValuesType): string[] => {\n  const values: string[] = [];\n  let currentValue = '';\n  let inQuotes = false;\n\n  for (let i = 0; i < line.length; i++) {\n    const char = line[i];\n\n    if (char === options.quote) {\n      inQuotes = !inQuotes;\n    } else if (char === options.delimiter && !inQuotes) {\n      values.push(currentValue.trim());\n      currentValue = '';\n    } else {\n      currentValue += char;\n    }\n  }\n\n  values.push(currentValue.trim());\n  return values;\n};\n\nconst parseValue = (value: string, dynamicTypes: boolean): any => {\n  if (!dynamicTypes) return value;\n\n  if (value.toLowerCase() === 'true') return true;\n  if (value.toLowerCase() === 'false') return false;\n  if (value === 'null') return null;\n  if (!isNaN(Number(value))) return Number(value);\n\n  return value;\n};\n\nfunction containsOnlyCustomCharAndSpaces(str: string, customChar: string) {\n  const regex = new RegExp(`^[${customChar}\\\\s]*$`);\n  return regex.test(str);\n}\n"
  },
  {
    "path": "src/pages/tools/csv/tsv-to-json/types.ts",
    "content": "export type InitialValuesType = {\n  delimiter: string;\n  quote: string;\n  comment: string;\n  useHeaders: boolean;\n  skipEmptyLines: boolean;\n  dynamicTypes: boolean;\n  indentationType: 'tab' | 'space' | 'none';\n  spacesCount: number;\n};\n"
  },
  {
    "path": "src/pages/tools/image/generic/change-colors/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport ColorSelector from '@components/options/ColorSelector';\nimport Color from 'color';\nimport TextFieldWithDesc from 'components/options/TextFieldWithDesc';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolImageInput from '@components/input/ToolImageInput';\nimport { processImage } from './service';\n\nconst initialValues = {\n  fromColor: 'white',\n  toColor: 'black',\n  similarity: '10'\n};\nconst validationSchema = Yup.object({\n  // splitSeparator: Yup.string().required('The separator is required')\n});\nexport default function ChangeColorsInImage({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n\n  const compute = (optionsValues: typeof initialValues, input: any) => {\n    if (!input) return;\n    const { fromColor, toColor, similarity } = optionsValues;\n    let fromRgb: [number, number, number];\n    let toRgb: [number, number, number];\n    try {\n      //@ts-ignore\n      fromRgb = Color(fromColor).rgb().array();\n      //@ts-ignore\n      toRgb = Color(toColor).rgb().array();\n    } catch (err) {\n      return;\n    }\n\n    processImage(input, fromRgb, toRgb, Number(similarity), setResult);\n  };\n\n  const getGroups: GetGroupsType<typeof initialValues> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'From color and to color',\n      component: (\n        <Box>\n          <ColorSelector\n            value={values.fromColor}\n            onColorChange={(val) => updateField('fromColor', val)}\n            description={'Replace this color (from color)'}\n            inputProps={{ 'data-testid': 'from-color-input' }}\n          />\n          <ColorSelector\n            value={values.toColor}\n            onColorChange={(val) => updateField('toColor', val)}\n            description={'With this color (to color)'}\n            inputProps={{ 'data-testid': 'to-color-input' }}\n          />\n          <TextFieldWithDesc\n            value={values.similarity}\n            onOwnChange={(val) => updateField('similarity', val)}\n            description={\n              'Match this % of similar colors of the from color. For example, 10% white will match white and a little bit of gray.'\n            }\n          />\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      validationSchema={validationSchema}\n      inputComponent={\n        <ToolImageInput\n          value={input}\n          onChange={setInput}\n          accept={['image/*']}\n          title={'Input image'}\n        />\n      }\n      resultComponent={<ToolFileResult title={'Result image'} value={result} />}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/change-colors/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:changeColors.title',\n    description: 'image:changeColors.description',\n    shortDescription: 'image:changeColors.shortDescription'\n  },\n\n  path: 'change-colors',\n  icon: 'cil:color-fill',\n\n  keywords: ['png', 'jpg', 'replace', 'swap'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/change-colors/service.ts",
    "content": "import { areColorsSimilar } from '@utils/color';\n\nexport const processImage = async (\n  file: File,\n  fromColor: [number, number, number],\n  toColor: [number, number, number],\n  similarity: number,\n  setResult: (result: File | null) => void\n): Promise<void> => {\n  if (file.type === 'image/svg+xml') {\n    const reader = new FileReader();\n    reader.onload = (e) => {\n      if (!e.target?.result) return;\n\n      let svgContent = e.target.result as string;\n      const toColorHex = rgbToHex(toColor[0], toColor[1], toColor[2]);\n\n      // Replace hex colors with various formats (#fff, #ffffff)\n      const hexRegexShort = new RegExp(`#[0-9a-f]{3}\\\\b`, 'gi');\n      const hexRegexLong = new RegExp(`#[0-9a-f]{6}\\\\b`, 'gi');\n\n      svgContent = svgContent.replace(hexRegexShort, (match) => {\n        // Expand short hex to full form for comparison\n        const expanded =\n          '#' + match[1] + match[1] + match[2] + match[2] + match[3] + match[3];\n        const matchRgb = hexToRgb(expanded);\n        if (matchRgb && areColorsSimilar(matchRgb, fromColor, similarity)) {\n          return toColorHex;\n        }\n        return match;\n      });\n\n      svgContent = svgContent.replace(hexRegexLong, (match) => {\n        const matchRgb = hexToRgb(match);\n        if (matchRgb && areColorsSimilar(matchRgb, fromColor, similarity)) {\n          return toColorHex;\n        }\n        return match;\n      });\n\n      // Replace RGB colors\n      const rgbRegex = new RegExp(\n        `rgb\\\\(\\\\s*(\\\\d+)\\\\s*,\\\\s*(\\\\d+)\\\\s*,\\\\s*(\\\\d+)\\\\s*\\\\)`,\n        'gi'\n      );\n      svgContent = svgContent.replace(rgbRegex, (match, r, g, b) => {\n        const matchRgb: [number, number, number] = [\n          parseInt(r),\n          parseInt(g),\n          parseInt(b)\n        ];\n        if (areColorsSimilar(matchRgb, fromColor, similarity)) {\n          return `rgb(${toColor[0]}, ${toColor[1]}, ${toColor[2]})`;\n        }\n        return match;\n      });\n\n      // Replace RGBA colors (preserving alpha)\n      const rgbaRegex = new RegExp(\n        `rgba\\\\(\\\\s*(\\\\d+)\\\\s*,\\\\s*(\\\\d+)\\\\s*,\\\\s*(\\\\d+)\\\\s*,\\\\s*([\\\\d.]+)\\\\s*\\\\)`,\n        'gi'\n      );\n      svgContent = svgContent.replace(rgbaRegex, (match, r, g, b, a) => {\n        const matchRgb: [number, number, number] = [\n          parseInt(r),\n          parseInt(g),\n          parseInt(b)\n        ];\n        if (areColorsSimilar(matchRgb, fromColor, similarity)) {\n          return `rgba(${toColor[0]}, ${toColor[1]}, ${toColor[2]}, ${a})`;\n        }\n        return match;\n      });\n\n      // Replace named SVG colors if they match our target color\n      const namedColors = {\n        red: [255, 0, 0],\n        green: [0, 128, 0],\n        blue: [0, 0, 255],\n        black: [0, 0, 0],\n        white: [255, 255, 255]\n        // Add more named colors as needed\n      };\n\n      Object.entries(namedColors).forEach(([name, rgb]) => {\n        if (\n          areColorsSimilar(\n            rgb as [number, number, number],\n            fromColor,\n            similarity\n          )\n        ) {\n          const colorRegex = new RegExp(`\\\\b${name}\\\\b`, 'gi');\n          svgContent = svgContent.replace(colorRegex, toColorHex);\n        }\n      });\n\n      // Create new file with modified content\n      const newFile = new File([svgContent], file.name, {\n        type: 'image/svg+xml'\n      });\n      setResult(newFile);\n    };\n    reader.readAsText(file);\n    return;\n  }\n  const canvas = document.createElement('canvas');\n  const ctx = canvas.getContext('2d');\n  if (ctx == null) return;\n  const img = new Image();\n\n  img.src = URL.createObjectURL(file);\n  await img.decode();\n\n  canvas.width = img.width;\n  canvas.height = img.height;\n  ctx.drawImage(img, 0, 0);\n\n  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\n  const data: Uint8ClampedArray = imageData.data;\n\n  for (let i = 0; i < data.length; i += 4) {\n    const currentColor: [number, number, number] = [\n      data[i],\n      data[i + 1],\n      data[i + 2]\n    ];\n    if (areColorsSimilar(currentColor, fromColor, similarity)) {\n      data[i] = toColor[0]; // Red\n      data[i + 1] = toColor[1]; // Green\n      data[i + 2] = toColor[2]; // Blue\n    }\n  }\n\n  ctx.putImageData(imageData, 0, 0);\n\n  canvas.toBlob((blob) => {\n    if (blob) {\n      const newFile = new File([blob], file.name, {\n        type: file.type\n      });\n      setResult(newFile);\n    }\n  }, file.type);\n};\n\nconst rgbToHex = (r: number, g: number, b: number): string => {\n  return (\n    '#' +\n    [r, g, b]\n      .map((x) => {\n        const hex = x.toString(16);\n        return hex.length === 1 ? '0' + hex : hex;\n      })\n      .join('')\n  );\n};\n\n// Helper function to parse hex to RGB\nconst hexToRgb = (hex: string): [number, number, number] | null => {\n  const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n  return result\n    ? [\n        parseInt(result[1], 16),\n        parseInt(result[2], 16),\n        parseInt(result[3], 16)\n      ]\n    : null;\n};\n"
  },
  {
    "path": "src/pages/tools/image/generic/change-opacity/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolImageInput from '@components/input/ToolImageInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport { changeOpacity } from './service';\nimport ToolContent from '@components/ToolContent';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { updateNumberField } from '@utils/string';\nimport { Box } from '@mui/material';\nimport SimpleRadio from '@components/options/SimpleRadio';\n\ntype InitialValuesType = {\n  opacity: number;\n  mode: 'solid' | 'gradient';\n  gradientType: 'linear' | 'radial';\n  gradientDirection: 'left-to-right' | 'inside-out';\n  areaLeft: number;\n  areaTop: number;\n  areaWidth: number;\n  areaHeight: number;\n};\n\nconst initialValues: InitialValuesType = {\n  opacity: 0.5,\n  mode: 'solid',\n  gradientType: 'linear',\n  gradientDirection: 'left-to-right',\n  areaLeft: 0,\n  areaTop: 0,\n  areaWidth: 100,\n  areaHeight: 100\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Semi-transparent PNG',\n    description: 'Make an image 50% transparent',\n    sampleOptions: {\n      opacity: 0.5,\n      mode: 'solid',\n      gradientType: 'linear',\n      gradientDirection: 'left-to-right',\n      areaLeft: 0,\n      areaTop: 0,\n      areaWidth: 100,\n      areaHeight: 100\n    },\n    sampleResult: ''\n  },\n  {\n    title: 'Slightly Faded PNG',\n    description: 'Create a subtle transparency effect',\n    sampleOptions: {\n      opacity: 0.8,\n      mode: 'solid',\n      gradientType: 'linear',\n      gradientDirection: 'left-to-right',\n      areaLeft: 0,\n      areaTop: 0,\n      areaWidth: 100,\n      areaHeight: 100\n    },\n    sampleResult: ''\n  },\n  {\n    title: 'Radial Gradient Opacity',\n    description: 'Apply a radial gradient opacity effect',\n    sampleOptions: {\n      opacity: 0.8,\n      mode: 'gradient',\n      gradientType: 'radial',\n      gradientDirection: 'inside-out',\n      areaLeft: 25,\n      areaTop: 25,\n      areaWidth: 50,\n      areaHeight: 50\n    },\n    sampleResult: ''\n  }\n];\n\nexport default function ChangeOpacity({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n\n  const compute = (values: InitialValuesType, input: any) => {\n    if (input) {\n      changeOpacity(input, values).then(setResult);\n    }\n  };\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolImageInput\n          value={input}\n          onChange={setInput}\n          accept={['image/*']}\n          title={'Input image'}\n        />\n      }\n      resultComponent={\n        <ToolFileResult title={'Changed image'} value={result} />\n      }\n      initialValues={initialValues}\n      // exampleCards={exampleCards}\n      getGroups={({ values, updateField }) => [\n        {\n          title: 'Opacity Settings',\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                description=\"Set opacity between 0 (transparent) and 1 (opaque)\"\n                value={values.opacity}\n                onOwnChange={(val) =>\n                  updateNumberField(val, 'opacity', updateField)\n                }\n                type=\"number\"\n                inputProps={{ step: 0.1, min: 0, max: 1 }}\n              />\n              <SimpleRadio\n                onClick={() => updateField('mode', 'solid')}\n                checked={values.mode === 'solid'}\n                description={'Set the same opacity level for all pixels'}\n                title={'Apply Solid Opacity'}\n              />\n              <SimpleRadio\n                onClick={() => updateField('mode', 'gradient')}\n                checked={values.mode === 'gradient'}\n                description={'Change opacity in a gradient'}\n                title={'Apply Gradient Opacity'}\n              />\n            </Box>\n          )\n        },\n        {\n          title: 'Gradient Options',\n          component: (\n            <Box>\n              <SimpleRadio\n                onClick={() => updateField('gradientType', 'linear')}\n                checked={values.gradientType === 'linear'}\n                description={'Linear opacity direction'}\n                title={'Linear Gradient'}\n              />\n              <SimpleRadio\n                onClick={() => updateField('gradientType', 'radial')}\n                checked={values.gradientType === 'radial'}\n                description={'Radial opacity direction'}\n                title={'Radial Gradient'}\n              />\n            </Box>\n          )\n        },\n        {\n          title: 'Opacity Area',\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                description=\"Left position\"\n                value={values.areaLeft}\n                onOwnChange={(val) =>\n                  updateNumberField(val, 'areaLeft', updateField)\n                }\n                type=\"number\"\n              />\n              <TextFieldWithDesc\n                description=\"Top position\"\n                value={values.areaTop}\n                onOwnChange={(val) =>\n                  updateNumberField(val, 'areaTop', updateField)\n                }\n                type=\"number\"\n              />\n              <TextFieldWithDesc\n                description=\"Width\"\n                value={values.areaWidth}\n                onOwnChange={(val) =>\n                  updateNumberField(val, 'areaWidth', updateField)\n                }\n                type=\"number\"\n              />\n              <TextFieldWithDesc\n                description=\"Height\"\n                value={values.areaHeight}\n                onOwnChange={(val) =>\n                  updateNumberField(val, 'areaHeight', updateField)\n                }\n                type=\"number\"\n              />\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/change-opacity/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:changeOpacity.title',\n    description: 'image:changeOpacity.description',\n    shortDescription: 'image:changeOpacity.shortDescription'\n  },\n\n  path: 'change-opacity',\n  icon: 'material-symbols:opacity',\n\n  keywords: ['opacity', 'transparency', 'png', 'alpha', 'jpg', 'jpeg', 'image'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/change-opacity/service.ts",
    "content": "interface OpacityOptions {\n  opacity: number;\n  mode: 'solid' | 'gradient';\n  gradientType: 'linear' | 'radial';\n  gradientDirection: 'left-to-right' | 'inside-out';\n  areaLeft: number;\n  areaTop: number;\n  areaWidth: number;\n  areaHeight: number;\n}\n\nexport async function changeOpacity(\n  file: File,\n  options: OpacityOptions\n): Promise<File> {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.onload = (event) => {\n      const img = new Image();\n      img.onload = () => {\n        const canvas = document.createElement('canvas');\n        const ctx = canvas.getContext('2d');\n        if (!ctx) {\n          reject(new Error('Canvas context not supported'));\n          return;\n        }\n        canvas.width = img.width;\n        canvas.height = img.height;\n\n        if (options.mode === 'solid') {\n          applySolidOpacity(ctx, img, options);\n        } else {\n          applyGradientOpacity(ctx, img, options);\n        }\n\n        canvas.toBlob((blob) => {\n          if (blob) {\n            const newFile = new File([blob], file.name, { type: file.type });\n            resolve(newFile);\n          } else {\n            reject(new Error('Failed to generate image blob'));\n          }\n        }, file.type);\n      };\n      img.onerror = () => reject(new Error('Failed to load image'));\n      img.src = event.target?.result as string;\n    };\n    reader.onerror = () => reject(new Error('Failed to read file'));\n    reader.readAsDataURL(file);\n  });\n}\n\nfunction applySolidOpacity(\n  ctx: CanvasRenderingContext2D,\n  img: HTMLImageElement,\n  options: OpacityOptions\n) {\n  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n  ctx.globalAlpha = options.opacity;\n  ctx.drawImage(img, 0, 0);\n}\n\nfunction applyGradientOpacity(\n  ctx: CanvasRenderingContext2D,\n  img: HTMLImageElement,\n  options: OpacityOptions\n) {\n  const { areaLeft, areaTop, areaWidth, areaHeight } = options;\n\n  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n  ctx.drawImage(img, 0, 0);\n\n  const gradient =\n    options.gradientType === 'linear'\n      ? createLinearGradient(ctx, options)\n      : createRadialGradient(ctx, options);\n\n  ctx.fillStyle = gradient;\n  ctx.fillRect(areaLeft, areaTop, areaWidth, areaHeight);\n}\n\nfunction createLinearGradient(\n  ctx: CanvasRenderingContext2D,\n  options: OpacityOptions\n) {\n  const { areaLeft, areaTop, areaWidth, areaHeight } = options;\n  const gradient = ctx.createLinearGradient(\n    areaLeft,\n    areaTop,\n    areaLeft + areaWidth,\n    areaTop\n  );\n  gradient.addColorStop(0, `rgba(255,255,255,${options.opacity})`);\n  gradient.addColorStop(1, 'rgba(255,255,255,0)');\n  return gradient;\n}\n\nfunction createRadialGradient(\n  ctx: CanvasRenderingContext2D,\n  options: OpacityOptions\n) {\n  const { areaLeft, areaTop, areaWidth, areaHeight } = options;\n  const centerX = areaLeft + areaWidth / 2;\n  const centerY = areaTop + areaHeight / 2;\n  const radius = Math.min(areaWidth, areaHeight) / 2;\n\n  const gradient = ctx.createRadialGradient(\n    centerX,\n    centerY,\n    0,\n    centerX,\n    centerY,\n    radius\n  );\n\n  if (options.gradientDirection === 'inside-out') {\n    gradient.addColorStop(0, `rgba(255,255,255,${options.opacity})`);\n    gradient.addColorStop(1, 'rgba(255,255,255,0)');\n  } else {\n    gradient.addColorStop(0, 'rgba(255,255,255,0)');\n    gradient.addColorStop(1, `rgba(255,255,255,${options.opacity})`);\n  }\n\n  return gradient;\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/compress/index.tsx",
    "content": "import React, { useContext, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { InitialValuesType } from './types';\nimport { compressImages } from './service';\nimport ToolContent from '@components/ToolContent';\nimport ToolMultipleImageInput, {\n  MultiImageInput\n} from '@components/input/ToolMultipleImageInput';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolMultiFileResult from '@components/result/ToolMultiFileResult';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { Box } from '@mui/material';\nimport Typography from '@mui/material/Typography';\nimport { CustomSnackBarContext } from '../../../../../contexts/CustomSnackBarContext';\nimport { updateNumberField } from '@utils/string';\n\nconst initialValues: InitialValuesType = {\n  maxFileSizeInMB: 1.0,\n  quality: 80\n};\n\nexport default function CompressImage({ title }: ToolComponentProps) {\n  const { t } = useTranslation('image');\n  const [input, setInput] = useState<MultiImageInput[]>([]);\n  const [results, setResults] = useState<File[]>([]);\n  const [zipFile, setZipFile] = useState<File | null>(null);\n  const [isProcessing, setIsProcessing] = useState<boolean>(false);\n  const [originalSize, setOriginalSize] = useState<number | null>(null); // Store original file size\n  const [compressedSize, setCompressedSize] = useState<number | null>(null); // Store compressed file size\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n\n  const compute = async (\n    values: InitialValuesType,\n    input: MultiImageInput[]\n  ) => {\n    if (!input.length) return;\n\n    setOriginalSize(input.reduce((acc, img) => acc + img.file.size, 0));\n\n    try {\n      setIsProcessing(true);\n\n      const output = await compressImages(\n        input.map((img) => img.file),\n        values\n      );\n\n      if (!output) {\n        showSnackBar(t('compress.failedToCompress'), 'error');\n        return;\n      }\n\n      if (output.results.length < input.length) {\n        showSnackBar(t('compress.failedToCompress'), 'error');\n      }\n\n      setResults(output.results);\n      setZipFile(output.zipFile);\n      setCompressedSize(output.results.reduce((acc, f) => acc + f.size, 0));\n    } catch (err) {\n      console.error('Error in compression:', err);\n    } finally {\n      setIsProcessing(false);\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolMultipleImageInput\n          value={input}\n          type={'image'}\n          onChange={setInput}\n          accept={['image/*']}\n          title={t('compress.inputTitle')}\n        />\n      }\n      resultComponent={\n        <ToolMultiFileResult\n          title={t('compress.resultTitle')}\n          value={results}\n          zipFile={zipFile}\n          loading={isProcessing}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('compress.compressionOptions'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                name=\"maxFileSizeInMB\"\n                type=\"number\"\n                inputProps={{ min: 0.1, step: 0.1 }}\n                description={t('compress.maxFileSizeDescription')}\n                onOwnChange={(value) =>\n                  updateNumberField(value, 'maxFileSizeInMB', updateField)\n                }\n                value={values.maxFileSizeInMB}\n              />\n              <TextFieldWithDesc\n                name=\"quality\"\n                type=\"number\"\n                inputProps={{ min: 10, max: 100, step: 1 }}\n                description={t('compress.qualityDescription')}\n                onOwnChange={(value) =>\n                  updateNumberField(value, 'quality', updateField)\n                }\n                value={values.quality}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('compress.fileSizes'),\n          component: (\n            <Box>\n              <Box>\n                {originalSize !== null && (\n                  <Typography>\n                    {t('compress.originalSize')}:{' '}\n                    {(originalSize / 1024).toFixed(2)} KB\n                  </Typography>\n                )}\n                {compressedSize !== null && (\n                  <Typography>\n                    {t('compress.compressedSize')}:{' '}\n                    {(compressedSize / 1024).toFixed(2)} KB\n                  </Typography>\n                )}\n              </Box>\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n      setInput={setInput}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/compress/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:compress.title',\n    description: 'image:compress.description',\n    shortDescription: 'image:compress.shortDescription',\n    userTypes: ['generalUsers']\n  },\n\n  path: 'compress',\n  icon: 'material-symbols-light:compress-rounded',\n\n  keywords: ['image', 'compress', 'reduce', 'quality'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/compress/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport imageCompression from 'browser-image-compression';\nimport JSZip from 'jszip';\n\nexport const compressImages = async (\n  files: File[],\n  options: InitialValuesType\n): Promise<{ results: File[]; zipFile: File } | null> => {\n  try {\n    const { maxFileSizeInMB, quality } = options;\n\n    // Configuration for the compression library\n    const compressionOptions = {\n      maxSizeMB: maxFileSizeInMB,\n      maxWidthOrHeight: 1920, // Reasonable default for most use cases\n      useWebWorker: true,\n      initialQuality: quality / 100 // Convert percentage to decimal\n    };\n\n    // Compress the given images\n    const compressed = await Promise.all(\n      files.map(async (file) => {\n        try {\n          const compressedFile = await imageCompression(\n            file,\n            compressionOptions\n          );\n          return new File([compressedFile], file.name, {\n            type: compressedFile.type\n          });\n        } catch (error) {\n          console.error(`Error compressing ${file.name}:`, error);\n          return null;\n        }\n      })\n    );\n\n    const results = compressed.filter((f): f is File => f !== null);\n\n    if (results.length === 0) return null;\n\n    const zip = new JSZip();\n    results.forEach((file) => zip.file(file.name, file));\n    const zipBlob = await zip.generateAsync({ type: 'blob' });\n    const zipFile = new File([zipBlob], 'compressed-images.zip', {\n      type: 'application/zip'\n    });\n\n    return { results, zipFile };\n  } catch (error) {\n    console.error('Error compressing images:', error);\n    return null;\n  }\n};\n"
  },
  {
    "path": "src/pages/tools/image/generic/compress/types.ts",
    "content": "export interface InitialValuesType {\n  maxFileSizeInMB: number;\n  quality: number;\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/convert-to-jpg/index.tsx",
    "content": "import { Box, Slider, Typography } from '@mui/material';\nimport ToolImageInput from 'components/input/ToolImageInput';\nimport ColorSelector from 'components/options/ColorSelector';\nimport ToolFileResult from 'components/result/ToolFileResult';\nimport Color from 'color';\nimport React, { useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\n\nconst initialValues = {\n  quality: 85,\n  backgroundColor: '#ffffff'\n};\n\nconst validationSchema = Yup.object({\n  quality: Yup.number().min(1).max(100).required('Quality is required'),\n  backgroundColor: Yup.string().required('Background color is required')\n});\n\nexport default function ConvertToJpg({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n\n  const compute = async (\n    optionsValues: typeof initialValues,\n    input: any\n  ): Promise<void> => {\n    if (!input) return;\n\n    const processImage = async (\n      file: File,\n      quality: number,\n      backgroundColor: string\n    ) => {\n      const canvas = document.createElement('canvas');\n      const ctx = canvas.getContext('2d');\n      if (ctx == null) return;\n\n      const img = new Image();\n      img.src = URL.createObjectURL(file);\n\n      try {\n        await img.decode();\n\n        canvas.width = img.width;\n        canvas.height = img.height;\n\n        // Fill background with selected color (important for transparency)\n        let bgColor: [number, number, number];\n        try {\n          //@ts-ignore\n          bgColor = Color(backgroundColor).rgb().array();\n        } catch (err) {\n          bgColor = [255, 255, 255]; // Default to white\n        }\n\n        ctx.fillStyle = `rgb(${bgColor[0]}, ${bgColor[1]}, ${bgColor[2]})`;\n        ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n        // Draw the image on top\n        ctx.drawImage(img, 0, 0);\n\n        // Convert to JPG with specified quality\n        canvas.toBlob(\n          (blob) => {\n            if (blob) {\n              const fileName = file.name.replace(/\\.[^/.]+$/, '') + '.jpg';\n              const newFile = new File([blob], fileName, {\n                type: 'image/jpeg'\n              });\n              setResult(newFile);\n            }\n          },\n          'image/jpeg',\n          quality / 100\n        );\n      } catch (error) {\n        console.error('Error processing image:', error);\n      } finally {\n        URL.revokeObjectURL(img.src);\n      }\n    };\n\n    processImage(input, optionsValues.quality, optionsValues.backgroundColor);\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolImageInput\n          value={input}\n          onChange={setInput}\n          accept={[\n            'image/png',\n            'image/gif',\n            'image/tiff',\n            'image/tif',\n            'image/webp',\n            'image/svg+xml',\n            'image/heic',\n            'image/heif',\n            'image/raw',\n            'image/x-adobe-dng',\n            'image/x-canon-cr2',\n            'image/x-canon-crw',\n            'image/x-nikon-nef',\n            'image/x-sony-arw',\n            'image/vnd.adobe.photoshop'\n          ]}\n          title={'Input Image'}\n        />\n      }\n      resultComponent={\n        <ToolFileResult title={'Output JPG'} value={result} extension={'jpg'} />\n      }\n      initialValues={initialValues}\n      validationSchema={validationSchema}\n      getGroups={({ values, updateField }) => [\n        {\n          title: 'JPG Quality Settings',\n          component: (\n            <Box>\n              <Box mb={3}>\n                <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n                  JPG Quality: {values.quality}%\n                </Typography>\n                <Slider\n                  value={values.quality}\n                  onChange={(_, value) =>\n                    updateField(\n                      'quality',\n                      Array.isArray(value) ? value[0] : value\n                    )\n                  }\n                  min={1}\n                  max={100}\n                  step={1}\n                  valueLabelDisplay=\"auto\"\n                  valueLabelFormat={(value) => `${value}%`}\n                  sx={{ mt: 1 }}\n                />\n                <Typography variant=\"caption\" color=\"text.secondary\">\n                  Higher values = better quality, larger file size\n                </Typography>\n              </Box>\n\n              <ColorSelector\n                value={values.backgroundColor}\n                onColorChange={(val) => updateField('backgroundColor', val)}\n                description={'Background color (for transparent images)'}\n                inputProps={{ 'data-testid': 'background-color-input' }}\n              />\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n      setInput={setInput}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/convert-to-jpg/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:convertToJpg.title',\n    description: 'image:convertToJpg.description',\n    shortDescription: 'image:convertToJpg.shortDescription'\n  },\n\n  path: 'convert-to-jpg',\n  icon: 'ph:file-jpg-thin',\n\n  keywords: [\n    'convert',\n    'jpg',\n    'jpeg',\n    'png',\n    'gif',\n    'tiff',\n    'webp',\n    'heic',\n    'raw',\n    'psd',\n    'svg',\n    'quality',\n    'compression'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/create-transparent/create-transparent.e2e.spec.ts",
    "content": "import { expect, test } from '@playwright/test';\nimport { Buffer } from 'buffer';\nimport path from 'path';\nimport Jimp from 'jimp';\n\ntest.describe('Create transparent PNG', () => {\n  test.beforeEach(async ({ page }) => {\n    await page.goto('/image-generic/create-transparent');\n  });\n\n  //TODO check why failing\n  // test('should make png color transparent', async ({ page }) => {\n  //   // Upload image\n  //   const fileInput = page.locator('input[type=\"file\"]');\n  //   const imagePath = path.join(__dirname, 'test.png');\n  //   await fileInput?.setInputFiles(imagePath);\n  //\n  //   await page.getByTestId('color-input').fill('#FF0000');\n  //\n  //   // Click on download\n  //   const downloadPromise = page.waitForEvent('download');\n  //   await page.getByText('Save as').click();\n  //\n  //   // Intercept and read downloaded PNG\n  //   const download = await downloadPromise;\n  //   const downloadStream = await download.createReadStream();\n  //\n  //   const chunks = [];\n  //   for await (const chunk of downloadStream) {\n  //     chunks.push(chunk);\n  //   }\n  //   const fileContent = Buffer.concat(chunks);\n  //\n  //   expect(fileContent.length).toBeGreaterThan(0);\n  //\n  //   // Check that the first pixel is transparent\n  //   const image = await Jimp.read(fileContent);\n  //   const color = image.getPixelColor(0, 0);\n  //   expect(color).toBe(0);\n  // });\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/create-transparent/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolImageInput from '@components/input/ToolImageInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport ColorSelector from '@components/options/ColorSelector';\nimport Color from 'color';\nimport TextFieldWithDesc from 'components/options/TextFieldWithDesc';\nimport { areColorsSimilar } from 'utils/color';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\n\nconst initialValues = {\n  fromColor: 'white',\n  similarity: '10'\n};\n\nconst validationSchema = Yup.object({\n  // splitSeparator: Yup.string().required('The separator is required')\n});\n\nexport default function CreateTransparent({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n\n  const compute = (optionsValues: typeof initialValues, input: any) => {\n    if (!input) return;\n    const { fromColor, similarity } = optionsValues;\n\n    let fromRgb: [number, number, number];\n    try {\n      //@ts-ignore\n      fromRgb = Color(fromColor).rgb().array();\n    } catch (err) {\n      return;\n    }\n    const processImage = async (\n      file: File,\n      fromColor: [number, number, number],\n      similarity: number\n    ) => {\n      const canvas = document.createElement('canvas');\n      const ctx = canvas.getContext('2d');\n      if (ctx == null) return;\n      const img = new Image();\n\n      img.src = URL.createObjectURL(file);\n      await img.decode();\n\n      canvas.width = img.width;\n      canvas.height = img.height;\n      ctx.drawImage(img, 0, 0);\n\n      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\n      const data: Uint8ClampedArray = imageData.data;\n\n      for (let i = 0; i < data.length; i += 4) {\n        const currentColor: [number, number, number] = [\n          data[i],\n          data[i + 1],\n          data[i + 2]\n        ];\n        if (areColorsSimilar(currentColor, fromColor, similarity)) {\n          data[i + 3] = 0; // Set alpha to 0 (transparent)\n        }\n      }\n\n      ctx.putImageData(imageData, 0, 0);\n\n      canvas.toBlob((blob) => {\n        if (blob) {\n          const newFile = new File([blob], file.name, { type: 'image/png' });\n          setResult(newFile);\n        }\n      }, 'image/png');\n    };\n\n    processImage(input, fromRgb, Number(similarity));\n  };\n\n  const getGroups: GetGroupsType<typeof initialValues> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'From color and similarity',\n      component: (\n        <Box>\n          <ColorSelector\n            value={values.fromColor}\n            onColorChange={(val) => updateField('fromColor', val)}\n            description={'Replace this color (from color)'}\n            inputProps={{ 'data-testid': 'color-input' }}\n          />\n          <TextFieldWithDesc\n            value={values.similarity}\n            onOwnChange={(val) => updateField('similarity', val)}\n            description={\n              'Match this % of similar colors of the from color. For example, 10% white will match white and a little bit of gray.'\n            }\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolImageInput\n          value={input}\n          onChange={setInput}\n          accept={['image/*']}\n          title={'Input image'}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          title={'Transparent PNG'}\n          value={result}\n          extension={'png'}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      validationSchema={validationSchema}\n      toolInfo={{\n        title: 'Create Transparent PNG',\n        description:\n          'This tool allows you to make specific colors in an image transparent. You can select the color to replace and adjust the similarity threshold to include similar colors.'\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/create-transparent/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:createTransparent.title',\n    description: 'image:createTransparent.description',\n    shortDescription: 'image:createTransparent.shortDescription'\n  },\n\n  path: 'create-transparent',\n  icon: 'mdi:circle-transparent',\n\n  keywords: ['create', 'transparent'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/crop/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolImageInput from '@components/input/ToolImageInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport { GetGroupsType, UpdateField } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport SimpleRadio from '@components/options/SimpleRadio';\n\nconst initialValues = {\n  xPosition: '0',\n  yPosition: '0',\n  cropWidth: '100',\n  cropHeight: '100',\n  cropShape: 'rectangular' as 'rectangular' | 'circular'\n};\ntype InitialValuesType = typeof initialValues;\nconst validationSchema = Yup.object({\n  xPosition: Yup.number()\n    .min(0, 'X position must be positive')\n    .required('X position is required'),\n  yPosition: Yup.number()\n    .min(0, 'Y position must be positive')\n    .required('Y position is required'),\n  cropWidth: Yup.number()\n    .min(1, 'Width must be at least 1px')\n    .required('Width is required'),\n  cropHeight: Yup.number()\n    .min(1, 'Height must be at least 1px')\n    .required('Height is required')\n});\n\nexport default function CropImage({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n\n  const compute = (optionsValues: InitialValuesType, input: any) => {\n    if (!input) return;\n\n    const { xPosition, yPosition, cropWidth, cropHeight, cropShape } =\n      optionsValues;\n    const x = parseInt(xPosition);\n    const y = parseInt(yPosition);\n    const width = parseInt(cropWidth);\n    const height = parseInt(cropHeight);\n    const isCircular = cropShape === 'circular';\n\n    const processImage = async (\n      file: File,\n      x: number,\n      y: number,\n      width: number,\n      height: number,\n      isCircular: boolean\n    ) => {\n      // Create source canvas\n      const sourceCanvas = document.createElement('canvas');\n      const sourceCtx = sourceCanvas.getContext('2d');\n      if (sourceCtx == null) return;\n\n      // Create destination canvas\n      const destCanvas = document.createElement('canvas');\n      const destCtx = destCanvas.getContext('2d');\n      if (destCtx == null) return;\n\n      // Load image\n      const img = new Image();\n      img.src = URL.createObjectURL(file);\n      await img.decode();\n\n      // Set source canvas dimensions\n      sourceCanvas.width = img.width;\n      sourceCanvas.height = img.height;\n\n      // Draw original image on source canvas\n      sourceCtx.drawImage(img, 0, 0);\n\n      // Set destination canvas dimensions to crop size\n      destCanvas.width = width;\n      destCanvas.height = height;\n\n      if (isCircular) {\n        // For circular crop\n        destCtx.beginPath();\n        // Create a circle with center at half width/height and radius of half the smaller dimension\n        const radius = Math.min(width, height) / 2;\n        destCtx.arc(width / 2, height / 2, radius, 0, Math.PI * 2);\n        destCtx.closePath();\n        destCtx.clip();\n\n        // Draw the cropped portion centered in the circle\n        destCtx.drawImage(img, x, y, width, height, 0, 0, width, height);\n      } else {\n        // For rectangular crop, simply draw the specified region\n        destCtx.drawImage(img, x, y, width, height, 0, 0, width, height);\n      }\n\n      // Convert canvas to blob and create file\n      destCanvas.toBlob((blob) => {\n        if (blob) {\n          const newFile = new File([blob], file.name, {\n            type: file.type\n          });\n          setResult(newFile);\n        }\n      }, file.type);\n    };\n\n    processImage(input, x, y, width, height, isCircular);\n  };\n  const handleCropChange =\n    (values: InitialValuesType, updateField: UpdateField<InitialValuesType>) =>\n    (\n      position: { x: number; y: number },\n      size: { width: number; height: number }\n    ) => {\n      updateField('xPosition', position.x.toString());\n      updateField('yPosition', position.y.toString());\n      updateField('cropWidth', size.width.toString());\n      updateField('cropHeight', size.height.toString());\n    };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Crop Position and Size',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.xPosition}\n            onOwnChange={(val) => updateField('xPosition', val)}\n            description={'X position (in pixels)'}\n            inputProps={{\n              'data-testid': 'x-position-input',\n              type: 'number',\n              min: 0\n            }}\n          />\n          <TextFieldWithDesc\n            value={values.yPosition}\n            onOwnChange={(val) => updateField('yPosition', val)}\n            description={'Y position (in pixels)'}\n            inputProps={{\n              'data-testid': 'y-position-input',\n              type: 'number',\n              min: 0\n            }}\n          />\n          <TextFieldWithDesc\n            value={values.cropWidth}\n            onOwnChange={(val) => updateField('cropWidth', val)}\n            description={'Crop width (in pixels)'}\n            inputProps={{\n              'data-testid': 'crop-width-input',\n              type: 'number',\n              min: 1\n            }}\n          />\n          <TextFieldWithDesc\n            value={values.cropHeight}\n            onOwnChange={(val) => updateField('cropHeight', val)}\n            description={'Crop height (in pixels)'}\n            inputProps={{\n              'data-testid': 'crop-height-input',\n              type: 'number',\n              min: 1\n            }}\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Crop Shape',\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('cropShape', 'rectangular')}\n            checked={values.cropShape == 'rectangular'}\n            description={'Crop a rectangular fragment from an image.'}\n            title={'Rectangular Crop Shape'}\n          />\n          <SimpleRadio\n            onClick={() => updateField('cropShape', 'circular')}\n            checked={values.cropShape == 'circular'}\n            description={'Crop a circular fragment from an image.'}\n            title={'Circular Crop Shape'}\n          />\n        </Box>\n      )\n    }\n  ];\n  const renderCustomInput = (\n    values: InitialValuesType,\n    updateField: UpdateField<InitialValuesType>\n  ) => (\n    <ToolImageInput\n      value={input}\n      onChange={setInput}\n      accept={['image/*']}\n      title={'Input image'}\n      showCropOverlay={!!input}\n      cropShape={values.cropShape as 'rectangular' | 'circular'}\n      cropPosition={{\n        x: parseInt(values.xPosition || '0'),\n        y: parseInt(values.yPosition || '0')\n      }}\n      cropSize={{\n        width: parseInt(values.cropWidth || '100'),\n        height: parseInt(values.cropHeight || '100')\n      }}\n      onCropChange={handleCropChange(values, updateField)}\n    />\n  );\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      validationSchema={validationSchema}\n      renderCustomInput={renderCustomInput}\n      resultComponent={\n        <ToolFileResult title={'Cropped image'} value={result} />\n      }\n      toolInfo={{\n        title: 'Crop Image',\n        description:\n          'This tool allows you to crop an image by specifying the position, size, and shape of the crop area. You can choose between rectangular or circular cropping.'\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/crop/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:crop.title',\n    description: 'image:crop.description',\n    shortDescription: 'image:crop.shortDescription'\n  },\n\n  path: 'crop',\n  icon: 'mdi:crop', // Iconify icon as a string\n\n  keywords: ['crop', 'image', 'edit', 'resize', 'trim'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/editor/index.tsx",
    "content": "import React, { useCallback, useState } from 'react';\nimport { Box } from '@mui/material';\nimport ToolImageInput from '@components/input/ToolImageInput';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\n\n// Import the image editor with proper typing\nimport FilerobotImageEditor, {\n  FilerobotImageEditorConfig\n} from 'react-filerobot-image-editor';\n\nexport default function ImageEditor({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [isEditorOpen, setIsEditorOpen] = useState(false);\n  const [imageUrl, setImageUrl] = useState<string | null>(null);\n\n  // Handle file input change\n  const handleInputChange = useCallback((file: File | null) => {\n    setInput(file);\n    if (file) {\n      // Create object URL for the image editor\n      const url = URL.createObjectURL(file);\n      setImageUrl(url);\n      setIsEditorOpen(true);\n    } else {\n      setImageUrl(null);\n    }\n  }, []);\n\n  const onCloseEditor = (reason: string) => {\n    setIsEditorOpen(false);\n    setImageUrl(null);\n  };\n\n  // Handle save from image editor\n  const handleSave: FilerobotImageEditorConfig['onSave'] = (\n    editedImageObject,\n    designState\n  ) => {\n    if (editedImageObject && editedImageObject.imageBase64) {\n      // Convert base64 to blob\n      const base64Data = editedImageObject.imageBase64.split(',')[1];\n      const byteCharacters = atob(base64Data);\n      const byteNumbers = new Array(byteCharacters.length);\n\n      for (let i = 0; i < byteCharacters.length; i++) {\n        byteNumbers[i] = byteCharacters.charCodeAt(i);\n      }\n\n      const byteArray = new Uint8Array(byteNumbers);\n      const blob = new Blob([byteArray], { type: editedImageObject.mimeType });\n\n      const editedFile = new File(\n        [blob],\n        editedImageObject.fullName ?? 'edited.png',\n        {\n          type: editedImageObject.mimeType\n        }\n      );\n      // Create a temporary download link\n      const url = URL.createObjectURL(editedFile);\n      const a = document.createElement('a');\n      a.href = url;\n      a.download = editedFile.name; // This will be the name of the downloaded file\n      document.body.appendChild(a);\n      a.click();\n      document.body.removeChild(a);\n\n      // Release the blob URL\n      URL.revokeObjectURL(url);\n    }\n  };\n\n  const getDefaultImageName = () => {\n    if (!input) return;\n    const originalName = input?.name || 'edited-image';\n    const nameWithoutExt = originalName.replace(/\\.[^/.]+$/, '');\n    const editedFileName = `${nameWithoutExt}-edited`;\n    return editedFileName;\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={{}}\n      getGroups={null}\n      input={input}\n      inputComponent={\n        isEditorOpen ? (\n          imageUrl && (\n            <Box style={{ width: '100%', height: '70vh' }}>\n              <FilerobotImageEditor\n                source={imageUrl}\n                onSave={handleSave}\n                onClose={onCloseEditor}\n                annotationsCommon={{\n                  fill: 'blue'\n                }}\n                defaultSavedImageName={getDefaultImageName()}\n                Rotate={{ angle: 90, componentType: 'slider' }}\n                savingPixelRatio={1}\n                previewPixelRatio={1}\n              />\n            </Box>\n          )\n        ) : (\n          <ToolImageInput\n            value={input}\n            onChange={handleInputChange}\n            accept={['image/*']}\n            title=\"Upload Image to Edit\"\n          />\n        )\n      }\n      toolInfo={{\n        title: 'Image Editor',\n        description:\n          'A powerful image editing tool that provides professional-grade features including cropping, rotating, color adjustments, text annotations, drawing tools, and watermarking. Edit your images directly in your browser without the need for external software.'\n      }}\n      compute={() => {}}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/editor/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:editor.title',\n    description: 'image:editor.description',\n    shortDescription: 'image:editor.shortDescription'\n  },\n\n  path: 'editor',\n  icon: 'mdi:image-edit',\n\n  keywords: [\n    'image',\n    'editor',\n    'edit',\n    'crop',\n    'rotate',\n    'annotate',\n    'adjust',\n    'watermark',\n    'text',\n    'drawing',\n    'filters',\n    'brightness',\n    'contrast',\n    'saturation'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/image-to-text/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useContext, useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolImageInput from '@components/input/ToolImageInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport { extractTextFromImage, getAvailableLanguages } from './service';\nimport { InitialValuesType } from './types';\nimport { CustomSnackBarContext } from '../../../../../contexts/CustomSnackBarContext';\n\nconst initialValues: InitialValuesType = {\n  language: 'eng',\n  detectParagraphs: true\n};\n\nconst validationSchema = Yup.object({\n  language: Yup.string().required('Language is required')\n});\n\nexport default function ImageToText({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<string>('');\n  const [isProcessing, setIsProcessing] = useState<boolean>(false);\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n  const compute = async (optionsValues: InitialValuesType, input: any) => {\n    if (!input) return;\n\n    setIsProcessing(true);\n\n    try {\n      const extractedText = await extractTextFromImage(input, optionsValues);\n      setResult(extractedText);\n    } catch (err: any) {\n      showSnackBar(\n        err.message || 'An error occurred while processing the image',\n        'error'\n      );\n      setResult('');\n    } finally {\n      setIsProcessing(false);\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'OCR Options',\n      component: (\n        <Box>\n          <SelectWithDesc\n            selected={values.language}\n            onChange={(val) => updateField('language', val)}\n            description={\n              'Select the primary language in the image for better accuracy'\n            }\n            options={getAvailableLanguages()}\n          />\n          <CheckboxWithDesc\n            checked={values.detectParagraphs}\n            onChange={(value) => updateField('detectParagraphs', value)}\n            description={\n              'Attempt to preserve paragraph structure in the extracted text'\n            }\n            title={'Detect Paragraphs'}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      validationSchema={validationSchema}\n      inputComponent={\n        <ToolImageInput\n          value={input}\n          onChange={setInput}\n          accept={['image/jpeg', 'image/png']}\n          title={'Input Image'}\n        />\n      }\n      resultComponent={\n        <ToolTextResult\n          title={'Extracted Text'}\n          value={result}\n          loading={isProcessing}\n        />\n      }\n      toolInfo={{\n        title: 'Image to Text (OCR)',\n        description:\n          'This tool extracts text from images using Optical Character Recognition (OCR). Upload an image containing text, select the primary language, and get the extracted text. For best results, use clear images with good contrast.'\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/image-to-text/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:imageToText.title',\n    description: 'image:imageToText.description',\n    shortDescription: 'image:imageToText.shortDescription'\n  },\n\n  path: 'image-to-text',\n  icon: 'mdi:text-recognition', // Iconify icon as a string\n\n  keywords: [\n    'optical character recognition',\n    'extract',\n    'scan',\n    'tesseract',\n    'jpg',\n    'png'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/image-to-text/service.ts",
    "content": "import { createWorker } from 'tesseract.js';\nimport { InitialValuesType } from './types';\n\nexport const extractTextFromImage = async (\n  file: File,\n  options: InitialValuesType\n): Promise<string> => {\n  try {\n    const { language, detectParagraphs } = options;\n\n    // Create a Tesseract worker\n    const worker = await createWorker(language);\n\n    // Convert file to URL\n    const imageUrl = URL.createObjectURL(file);\n\n    // Recognize text\n    const { data } = await worker.recognize(imageUrl);\n\n    // Clean up\n    URL.revokeObjectURL(imageUrl);\n    await worker.terminate();\n\n    // Process the result based on options\n    if (detectParagraphs) {\n      // Return text with paragraph structure preserved\n      return data.text;\n    } else {\n      // Return plain text with basic formatting\n      return data.text;\n    }\n  } catch (error) {\n    console.error('Error extracting text from image:', error);\n    throw new Error(\n      'Failed to extract text from image. Please try again with a clearer image.'\n    );\n  }\n};\n\n// Helper function to get available languages\nexport const getAvailableLanguages = (): { value: string; label: string }[] => {\n  return [\n    { value: 'eng', label: 'English' },\n    { value: 'fra', label: 'French' },\n    { value: 'deu', label: 'German' },\n    { value: 'spa', label: 'Spanish' },\n    { value: 'ita', label: 'Italian' },\n    { value: 'por', label: 'Portuguese' },\n    { value: 'rus', label: 'Russian' },\n    { value: 'jpn', label: 'Japanese' },\n    { value: 'chi_sim', label: 'Chinese (Simplified)' },\n    { value: 'chi_tra', label: 'Chinese (Traditional)' },\n    { value: 'kor', label: 'Korean' },\n    { value: 'ara', label: 'Arabic' }\n  ];\n};\n"
  },
  {
    "path": "src/pages/tools/image/generic/image-to-text/types.ts",
    "content": "export type InitialValuesType = {\n  language: string;\n  detectParagraphs: boolean;\n};\n"
  },
  {
    "path": "src/pages/tools/image/generic/index.ts",
    "content": "import { tool as resizeImage } from './resize/meta';\nimport { tool as compressImage } from './compress/meta';\nimport { tool as changeColors } from './change-colors/meta';\nimport { tool as removeBackground } from './remove-background/meta';\nimport { tool as cropImage } from './crop/meta';\nimport { tool as changeOpacity } from './change-opacity/meta';\nimport { tool as createTransparent } from './create-transparent/meta';\nimport { tool as imageToText } from './image-to-text/meta';\nimport { tool as qrCodeGenerator } from './qr-code/meta';\nimport { tool as rotateImage } from './rotate/meta';\nimport { tool as convertToJpg } from './convert-to-jpg/meta';\nimport { tool as imageEditor } from './editor/meta';\nexport const imageGenericTools = [\n  imageEditor,\n  resizeImage,\n  compressImage,\n  removeBackground,\n  cropImage,\n  changeOpacity,\n  changeColors,\n  createTransparent,\n  imageToText,\n  qrCodeGenerator,\n  rotateImage,\n  convertToJpg\n];\n"
  },
  {
    "path": "src/pages/tools/image/generic/qr-code/index.tsx",
    "content": "import React, { useCallback, useState } from 'react';\nimport { Box } from '@mui/material';\nimport * as Yup from 'yup';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport { InitialValuesType, QRCodeType, WifiEncryptionType } from './types';\nimport ColorSelector from '@components/options/ColorSelector';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport { useTranslation } from 'react-i18next';\nimport * as QRCode from 'qrcode';\nimport { debounce } from 'lodash';\n\nconst initialValues: InitialValuesType = {\n  qrCodeType: 'URL',\n\n  // Common settings\n  size: '200',\n  bgColor: '#FFFFFF',\n  fgColor: '#000000',\n\n  // URL\n  url: 'https://example.com',\n\n  // Text\n  text: '',\n\n  // Email\n  emailAddress: '',\n  emailSubject: '',\n  emailBody: '',\n\n  // Phone\n  phoneNumber: '',\n\n  // SMS\n  smsNumber: '',\n  smsMessage: '',\n\n  // WiFi\n  wifiSsid: '',\n  wifiPassword: '',\n  wifiEncryption: 'WPA',\n\n  // vCard\n  vCardName: '',\n  vCardEmail: '',\n  vCardPhone: '',\n  vCardAddress: '',\n  vCardCompany: '',\n  vCardTitle: '',\n  vCardWebsite: ''\n};\n\n// Function to format the QR code data based on the type\nconst formatQRCodeData = (values: InitialValuesType): string => {\n  switch (values.qrCodeType) {\n    case 'URL':\n      return values.url;\n\n    case 'Text':\n      return values.text;\n\n    case 'Email': {\n      let emailData = `mailto:${values.emailAddress}`;\n      if (values.emailSubject || values.emailBody) {\n        emailData += '?';\n        if (values.emailSubject) {\n          emailData += `subject=${encodeURIComponent(values.emailSubject)}`;\n        }\n        if (values.emailBody) {\n          emailData += `${\n            values.emailSubject ? '&' : ''\n          }body=${encodeURIComponent(values.emailBody)}`;\n        }\n      }\n      return emailData;\n    }\n    case 'Phone':\n      return `tel:${values.phoneNumber}`;\n\n    case 'SMS':\n      return `sms:${values.smsNumber}${\n        values.smsMessage\n          ? `?body=${encodeURIComponent(values.smsMessage)}`\n          : ''\n      }`;\n\n    case 'WiFi': {\n      const encryption =\n        values.wifiEncryption === 'None' ? 'nopass' : values.wifiEncryption;\n      return `WIFI:T:${encryption};S:${values.wifiSsid};P:${values.wifiPassword};;`;\n    }\n    case 'vCard':\n      return `BEGIN:VCARD\nVERSION:3.0\nN:${values.vCardName}\nFN:${values.vCardName}\nORG:${values.vCardCompany}\nTITLE:${values.vCardTitle}\nTEL:${values.vCardPhone}\nEMAIL:${values.vCardEmail}\nADR:${values.vCardAddress}\nURL:${values.vCardWebsite}\nEND:VCARD`;\n\n    default:\n      return '';\n  }\n};\n\nconst validationSchema = Yup.object().shape({\n  qrCodeType: Yup.string().required('QR code type is required'),\n  size: Yup.number()\n    .min(100, 'Size must be at least 100px')\n    .max(1000, 'Size must be at most 1000px')\n    .required('Size is required'),\n  bgColor: Yup.string().required('Background color is required'),\n  fgColor: Yup.string().required('Foreground color is required'),\n\n  // URL\n  url: Yup.string().when('qrCodeType', {\n    is: 'URL',\n    then: (schema) =>\n      schema.url('Please enter a valid URL').required('URL is required')\n  }),\n\n  // Text\n  text: Yup.string().when('qrCodeType', {\n    is: 'Text',\n    then: (schema) => schema.required('Text is required')\n  }),\n\n  // Email\n  emailAddress: Yup.string().when('qrCodeType', {\n    is: 'Email',\n    then: (schema) =>\n      schema\n        .email('Please enter a valid email address')\n        .required('Email address is required')\n  }),\n\n  // Phone\n  phoneNumber: Yup.string().when('qrCodeType', {\n    is: 'Phone',\n    then: (schema) => schema.required('Phone number is required')\n  }),\n\n  // SMS\n  smsNumber: Yup.string().when('qrCodeType', {\n    is: 'SMS',\n    then: (schema) => schema.required('Phone number is required')\n  }),\n\n  // WiFi\n  wifiSsid: Yup.string().when('qrCodeType', {\n    is: 'WiFi',\n    then: (schema) => schema.required('SSID is required')\n  }),\n\n  // vCard\n  vCardName: Yup.string().when('qrCodeType', {\n    is: 'vCard',\n    then: (schema) => schema.required('Name is required')\n  })\n});\n\nexport default function QRCodeGenerator({ title }: ToolComponentProps) {\n  const { t } = useTranslation('image');\n  const [result, setResult] = useState<File | null>(null);\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => {\n    return [\n      {\n        title: 'QR Code Type',\n        component: (\n          <Box>\n            <SelectWithDesc\n              selected={values.qrCodeType}\n              onChange={(value) =>\n                updateField('qrCodeType', value as QRCodeType)\n              }\n              options={[\n                { label: 'URL', value: 'URL' },\n                { label: 'Text', value: 'Text' },\n                { label: 'Email', value: 'Email' },\n                { label: 'Phone', value: 'Phone' },\n                { label: 'SMS', value: 'SMS' },\n                { label: 'WiFi', value: 'WiFi' },\n                { label: 'vCard', value: 'vCard' }\n              ]}\n              description={t('qrCode.options.qrType')}\n            />\n          </Box>\n        )\n      },\n      {\n        title: 'QR Code Settings',\n        component: (\n          <Box>\n            <TextFieldWithDesc\n              value={values.size}\n              onOwnChange={(val) => updateField('size', val)}\n              description={t('qrCode.options.size')}\n              inputProps={{\n                type: 'number',\n                min: 100,\n                max: 1000\n              }}\n            />\n            <ColorSelector\n              description=\"Background Color\"\n              value={values.bgColor}\n              onColorChange={(val) => updateField('bgColor', val)}\n            />\n            <ColorSelector\n              description=\"Foreground Color\"\n              value={values.fgColor}\n              onColorChange={(val) => updateField('fgColor', val)}\n            />\n          </Box>\n        )\n      },\n      // Dynamic form fields based on QR code type\n      {\n        title: `${values.qrCodeType} Details`,\n        component: (\n          <Box>\n            {values.qrCodeType === 'URL' && (\n              <TextFieldWithDesc\n                value={values.url}\n                onOwnChange={(val) => updateField('url', val)}\n                description={t('qrCode.options.url')}\n                inputProps={{\n                  placeholder: 'https://example.com'\n                }}\n              />\n            )}\n\n            {values.qrCodeType === 'Text' && (\n              <TextFieldWithDesc\n                value={values.text}\n                onOwnChange={(val) => updateField('text', val)}\n                description={t('qrCode.options.text')}\n                multiline\n                rows={4}\n                inputProps={{\n                  placeholder: 'Lorem Ipsum'\n                }}\n              />\n            )}\n\n            {values.qrCodeType === 'Email' && (\n              <>\n                <TextFieldWithDesc\n                  value={values.emailAddress}\n                  onOwnChange={(val) => updateField('emailAddress', val)}\n                  description={t('qrCode.options.email')}\n                  inputProps={{\n                    placeholder: 'example@example.com',\n                    type: 'email'\n                  }}\n                />\n                <TextFieldWithDesc\n                  value={values.emailSubject}\n                  onOwnChange={(val) => updateField('emailSubject', val)}\n                  description={t('qrCode.options.emailSubject')}\n                  inputProps={{\n                    placeholder: 'Subject line'\n                  }}\n                />\n                <TextFieldWithDesc\n                  value={values.emailBody}\n                  onOwnChange={(val) => updateField('emailBody', val)}\n                  description={t('qrCode.options.emailBody')}\n                  multiline\n                  rows={4}\n                  inputProps={{\n                    placeholder: 'Body text'\n                  }}\n                />\n              </>\n            )}\n\n            {values.qrCodeType === 'Phone' && (\n              <TextFieldWithDesc\n                value={values.phoneNumber}\n                onOwnChange={(val) => updateField('phoneNumber', val)}\n                description={t('qrCode.options.phone')}\n                inputProps={{\n                  placeholder: '+1234567890',\n                  type: 'tel'\n                }}\n              />\n            )}\n\n            {values.qrCodeType === 'SMS' && (\n              <>\n                <TextFieldWithDesc\n                  value={values.smsNumber}\n                  onOwnChange={(val) => updateField('smsNumber', val)}\n                  description={t('qrCode.options.phone')}\n                  inputProps={{\n                    placeholder: '+1234567890',\n                    type: 'tel'\n                  }}\n                />\n                <TextFieldWithDesc\n                  value={values.smsMessage}\n                  onOwnChange={(val) => updateField('smsMessage', val)}\n                  description={t('qrCode.options.message')}\n                  multiline\n                  rows={4}\n                  inputProps={{\n                    placeholder: 'Lorem Ipsum'\n                  }}\n                />\n              </>\n            )}\n\n            {values.qrCodeType === 'WiFi' && (\n              <>\n                <TextFieldWithDesc\n                  value={values.wifiSsid}\n                  onOwnChange={(val) => updateField('wifiSsid', val)}\n                  description={t('qrCode.options.ssid')}\n                  inputProps={{\n                    placeholder: 'WIFI name'\n                  }}\n                />\n                <TextFieldWithDesc\n                  value={values.wifiPassword}\n                  onOwnChange={(val) => updateField('wifiPassword', val)}\n                  description={t('qrCode.options.password')}\n                  inputProps={{\n                    placeholder: '******',\n                    type: 'password'\n                  }}\n                />\n                <SelectWithDesc\n                  selected={values.wifiEncryption}\n                  onChange={(value) =>\n                    updateField('wifiEncryption', value as WifiEncryptionType)\n                  }\n                  options={[\n                    { label: 'WPA', value: 'WPA' },\n                    { label: 'WEP', value: 'WEP' },\n                    { label: 'None', value: 'None' }\n                  ]}\n                  description={t('qrCode.options.encryptionType')}\n                />\n              </>\n            )}\n\n            {values.qrCodeType === 'vCard' && (\n              <>\n                <TextFieldWithDesc\n                  value={values.vCardName}\n                  onOwnChange={(val) => updateField('vCardName', val)}\n                  description={t('qrCode.options.fullName')}\n                  inputProps={{\n                    placeholder: 'John Doe'\n                  }}\n                />\n                <TextFieldWithDesc\n                  value={values.vCardEmail}\n                  onOwnChange={(val) => updateField('vCardEmail', val)}\n                  description={t('qrCode.options.email')}\n                  inputProps={{\n                    placeholder: 'john@example.com',\n                    type: 'email'\n                  }}\n                />\n                <TextFieldWithDesc\n                  value={values.vCardPhone}\n                  onOwnChange={(val) => updateField('vCardPhone', val)}\n                  description={t('qrCode.options.phone')}\n                  inputProps={{\n                    placeholder: '+1234567890',\n                    type: 'tel'\n                  }}\n                />\n                <TextFieldWithDesc\n                  value={values.vCardAddress}\n                  onOwnChange={(val) => updateField('vCardAddress', val)}\n                  description={t('qrCode.options.address')}\n                  inputProps={{\n                    placeholder: '123 Main St, City, Country'\n                  }}\n                />\n                <TextFieldWithDesc\n                  value={values.vCardCompany}\n                  onOwnChange={(val) => updateField('vCardCompany', val)}\n                  description={t('qrCode.options.company')}\n                  inputProps={{\n                    placeholder: 'Company name'\n                  }}\n                />\n                <TextFieldWithDesc\n                  value={values.vCardTitle}\n                  onOwnChange={(val) => updateField('vCardTitle', val)}\n                  description={t('qrCode.options.job')}\n                  inputProps={{\n                    placeholder: 'Software Developer'\n                  }}\n                />\n                <TextFieldWithDesc\n                  value={values.vCardWebsite}\n                  onOwnChange={(val) => updateField('vCardWebsite', val)}\n                  description={t('qrCode.options.website')}\n                  inputProps={{\n                    placeholder: 'https://example.com'\n                  }}\n                />\n              </>\n            )}\n          </Box>\n        )\n      }\n    ];\n  };\n\n  const compute = async (options: InitialValuesType) => {\n    const qrValue = formatQRCodeData(options);\n    if (!qrValue) return;\n    const canvas = document.createElement('canvas');\n    QRCode.toDataURL(\n      canvas,\n      qrValue,\n      {\n        color: {\n          dark: options.fgColor,\n          light: options.bgColor\n        },\n        width: Number(options.size) || 200\n      },\n      async (error, url) => {\n        const res = await fetch(url);\n        const blob = await res.blob();\n        const file = new File([blob], 'Qr code.png', { type: 'image/png' });\n        setResult(file);\n      }\n    );\n  };\n  const debouncedCompute = useCallback(debounce(compute, 1000), []);\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      validationSchema={validationSchema}\n      compute={debouncedCompute}\n      resultComponent={\n        <ToolFileResult title={t('qrCode.resultOutput')} value={result} />\n      }\n      toolInfo={{\n        title: t('qrCode.title'),\n        description: t('qrCode.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/qr-code/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:qrCode.title',\n    description: 'image:qrCode.description',\n    shortDescription: 'image:qrCode.shortDescription'\n  },\n\n  path: 'qr-code',\n  icon: 'mdi:qrcode', // Iconify icon as a string\n  keywords: [\n    'qrcode',\n    'quick',\n    'read',\n    'generator',\n    'url',\n    'text',\n    'email',\n    'phone',\n    'sms',\n    'wifi',\n    'vcard',\n    'contact',\n    'barcode'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/qr-code/types.ts",
    "content": "export type QRCodeType =\n  | 'URL'\n  | 'Text'\n  | 'Email'\n  | 'Phone'\n  | 'SMS'\n  | 'WiFi'\n  | 'vCard';\n\nexport type WifiEncryptionType = 'WPA' | 'WEP' | 'None';\n\nexport interface InitialValuesType {\n  qrCodeType: QRCodeType;\n\n  // Common settings\n  size: string;\n  bgColor: string;\n  fgColor: string;\n\n  // URL\n  url: string;\n\n  // Text\n  text: string;\n\n  // Email\n  emailAddress: string;\n  emailSubject: string;\n  emailBody: string;\n\n  // Phone\n  phoneNumber: string;\n\n  // SMS\n  smsNumber: string;\n  smsMessage: string;\n\n  // WiFi\n  wifiSsid: string;\n  wifiPassword: string;\n  wifiEncryption: WifiEncryptionType;\n\n  // vCard\n  vCardName: string;\n  vCardEmail: string;\n  vCardPhone: string;\n  vCardAddress: string;\n  vCardCompany: string;\n  vCardTitle: string;\n  vCardWebsite: string;\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/remove-background/index.tsx",
    "content": "import React, { useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolImageInput from '@components/input/ToolImageInput';\nimport { removeBackground } from '@imgly/background-removal';\nimport * as heic2any from 'heic2any';\n\nconst initialValues = {};\n\nconst validationSchema = Yup.object({});\n\nexport default function RemoveBackgroundFromImage({\n  title\n}: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [isProcessing, setIsProcessing] = useState<boolean>(false);\n\n  const compute = async (_optionsValues: typeof initialValues, input: any) => {\n    if (!input) return;\n\n    setIsProcessing(true);\n\n    try {\n      let fileToProcess = input;\n      // Check if the file is HEIC (by MIME type or extension)\n      if (\n        input.type === 'image/heic' ||\n        input.name?.toLowerCase().endsWith('.heic')\n      ) {\n        // Convert HEIC to PNG using heic2any\n        const convertedBlob = await heic2any.default({\n          blob: input,\n          toType: 'image/png'\n        });\n        // heic2any returns a Blob or an array of Blobs\n        let pngBlob;\n        if (Array.isArray(convertedBlob)) {\n          pngBlob = convertedBlob[0];\n        } else {\n          pngBlob = convertedBlob;\n        }\n        fileToProcess = new File(\n          [pngBlob],\n          input.name.replace(/\\.[^/.]+$/, '') + '.png',\n          { type: 'image/png' }\n        );\n      }\n\n      // Convert the file to a Blob URL\n      const inputUrl = URL.createObjectURL(fileToProcess);\n\n      // Process the image with the background removal library\n      const blob = await removeBackground(inputUrl, {\n        progress: (progress) => {\n          console.log(`Background removal progress: ${progress}`);\n        }\n      });\n\n      // Create a new file from the blob\n      const newFile = new File(\n        [blob],\n        fileToProcess.name.replace(/\\.[^/.]+$/, '') + '-no-bg.png',\n        {\n          type: 'image/png'\n        }\n      );\n\n      setResult(newFile);\n    } catch (err) {\n      console.error('Error removing background:', err);\n      throw new Error(\n        'Failed to remove background. Please try a different image or try again later.'\n      );\n    } finally {\n      setIsProcessing(false);\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={null}\n      compute={compute}\n      input={input}\n      validationSchema={validationSchema}\n      inputComponent={\n        <ToolImageInput\n          value={input}\n          onChange={setInput}\n          accept={['image/*']}\n          title={'Input Image'}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          title={'Transparent PNG'}\n          value={result}\n          extension={'png'}\n          loading={isProcessing}\n          loadingText={'Removing background'}\n        />\n      }\n      toolInfo={{\n        title: 'Remove Background from Image',\n        description:\n          'This tool uses AI to automatically remove the background from your images, creating a transparent PNG. Perfect for product photos, profile pictures, and design assets.'\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/remove-background/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:removeBackground.title',\n    description: 'image:removeBackground.description',\n    shortDescription: 'image:removeBackground.shortDescription'\n  },\n\n  path: 'remove-background',\n  icon: 'mdi:image-remove',\n\n  keywords: ['png', 'transparent', 'image', 'ai', 'jpg', 'backing', 'backdrop'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/resize/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolImageInput from '@components/input/ToolImageInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { processImage } from './service';\nimport { InitialValuesType } from './types';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  resizeMethod: 'pixels' as 'pixels' | 'percentage',\n  dimensionType: 'width' as 'width' | 'height',\n  width: '800',\n  height: '600',\n  percentage: '50',\n  maintainAspectRatio: true\n};\n\nconst validationSchema = Yup.object({\n  width: Yup.number().when('resizeMethod', {\n    is: 'pixels',\n    then: (schema) =>\n      schema.min(1, 'Width must be at least 1px').required('Width is required')\n  }),\n  height: Yup.number().when('resizeMethod', {\n    is: 'pixels',\n    then: (schema) =>\n      schema\n        .min(1, 'Height must be at least 1px')\n        .required('Height is required')\n  }),\n  percentage: Yup.number().when('resizeMethod', {\n    is: 'percentage',\n    then: (schema) =>\n      schema\n        .min(1, 'Percentage must be at least 1%')\n        .max(1000, 'Percentage must be at most 1000%')\n        .required('Percentage is required')\n  })\n});\n\nexport default function ResizeImage({ title }: ToolComponentProps) {\n  const { t } = useTranslation('image');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n\n  const compute = async (optionsValues: InitialValuesType, input: any) => {\n    if (!input) return;\n    setResult(await processImage(input, optionsValues));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('resize.resizeMethod'),\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('resizeMethod', 'pixels')}\n            checked={values.resizeMethod === 'pixels'}\n            description={t('resize.resizeByPixelsDescription')}\n            title={t('resize.resizeByPixels')}\n          />\n          <SimpleRadio\n            onClick={() => updateField('resizeMethod', 'percentage')}\n            checked={values.resizeMethod === 'percentage'}\n            description={t('resize.resizeByPercentageDescription')}\n            title={t('resize.resizeByPercentage')}\n          />\n        </Box>\n      )\n    },\n    ...(values.resizeMethod === 'pixels'\n      ? [\n          {\n            title: t('resize.dimensionType'),\n            component: (\n              <Box>\n                <CheckboxWithDesc\n                  checked={values.maintainAspectRatio}\n                  onChange={(value) =>\n                    updateField('maintainAspectRatio', value)\n                  }\n                  description={t('resize.maintainAspectRatioDescription')}\n                  title={t('resize.maintainAspectRatio')}\n                />\n                {values.maintainAspectRatio && (\n                  <Box>\n                    <SimpleRadio\n                      onClick={() => updateField('dimensionType', 'width')}\n                      checked={values.dimensionType === 'width'}\n                      description={t('resize.setWidthDescription')}\n                      title={t('resize.setWidth')}\n                    />\n                    <SimpleRadio\n                      onClick={() => updateField('dimensionType', 'height')}\n                      checked={values.dimensionType === 'height'}\n                      description={t('resize.setHeightDescription')}\n                      title={t('resize.setHeight')}\n                    />\n                  </Box>\n                )}\n                <TextFieldWithDesc\n                  value={values.width}\n                  onOwnChange={(val) => updateField('width', val)}\n                  description={t('resize.widthDescription')}\n                  disabled={\n                    values.maintainAspectRatio &&\n                    values.dimensionType === 'height'\n                  }\n                  inputProps={{\n                    'data-testid': 'width-input',\n                    type: 'number',\n                    min: 1\n                  }}\n                />\n                <TextFieldWithDesc\n                  value={values.height}\n                  onOwnChange={(val) => updateField('height', val)}\n                  description={t('resize.heightDescription')}\n                  disabled={\n                    values.maintainAspectRatio &&\n                    values.dimensionType === 'width'\n                  }\n                  inputProps={{\n                    'data-testid': 'height-input',\n                    type: 'number',\n                    min: 1\n                  }}\n                />\n              </Box>\n            )\n          }\n        ]\n      : [\n          {\n            title: t('resize.percentage'),\n            component: (\n              <Box>\n                <TextFieldWithDesc\n                  value={values.percentage}\n                  onOwnChange={(val) => updateField('percentage', val)}\n                  description={t('resize.percentageDescription')}\n                  inputProps={{\n                    'data-testid': 'percentage-input',\n                    type: 'number',\n                    min: 1,\n                    max: 1000\n                  }}\n                />\n              </Box>\n            )\n          }\n        ])\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      validationSchema={validationSchema}\n      inputComponent={\n        <ToolImageInput\n          value={input}\n          onChange={setInput}\n          accept={['image/jpeg', 'image/png', 'image/svg+xml', 'image/gif']}\n          title={t('resize.inputTitle')}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          title={t('resize.resultTitle')}\n          value={result}\n          extension={input?.name.split('.').pop() || 'png'}\n        />\n      }\n      toolInfo={{\n        title: t('resize.toolInfo.title'),\n        description: t('resize.toolInfo.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/resize/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:resize.title',\n    description: 'image:resize.description',\n    shortDescription: 'image:resize.shortDescription',\n    userTypes: ['generalUsers']\n  },\n\n  path: 'resize',\n  icon: 'mdi:resize', // Iconify icon as a string\n\n  keywords: [\n    'resize',\n    'image',\n    'scale',\n    'jpg',\n    'png',\n    'svg',\n    'gif',\n    'dimensions'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/resize/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile, toBlobURL } from '@ffmpeg/util';\n\nexport const processImage = async (\n  file: File,\n  options: InitialValuesType\n): Promise<File | null> => {\n  const {\n    width,\n    height,\n    resizeMethod,\n    percentage,\n    dimensionType,\n    maintainAspectRatio\n  } = options;\n  if (file.type === 'image/svg+xml') {\n    try {\n      // Read the SVG file\n      const fileText = await file.text();\n      const parser = new DOMParser();\n      const svgDoc = parser.parseFromString(fileText, 'image/svg+xml');\n      const svgElement = svgDoc.documentElement;\n\n      // Get original dimensions\n      const viewBox = svgElement.getAttribute('viewBox');\n      let originalWidth: string | number | null =\n        svgElement.getAttribute('width');\n      let originalHeight: string | number | null =\n        svgElement.getAttribute('height');\n\n      // Parse viewBox if available and width/height are not explicitly set\n      let viewBoxValues = null;\n      if (viewBox) {\n        viewBoxValues = viewBox.split(' ').map(Number);\n      }\n\n      // Determine original dimensions from viewBox if not explicitly set\n      if (!originalWidth && viewBoxValues && viewBoxValues.length === 4) {\n        originalWidth = String(viewBoxValues[2]);\n      }\n      if (!originalHeight && viewBoxValues && viewBoxValues.length === 4) {\n        originalHeight = String(viewBoxValues[3]);\n      }\n\n      // Default dimensions if still not available\n      originalWidth = originalWidth ? parseFloat(originalWidth) : 300;\n      originalHeight = originalHeight ? parseFloat(originalHeight) : 150;\n\n      // Calculate new dimensions\n      let newWidth = originalWidth;\n      let newHeight = originalHeight;\n\n      if (resizeMethod === 'pixels') {\n        if (dimensionType === 'width') {\n          newWidth = parseInt(width);\n          if (maintainAspectRatio) {\n            newHeight = Math.round((newWidth / originalWidth) * originalHeight);\n          } else {\n            newHeight = parseInt(height);\n          }\n        } else {\n          // height\n          newHeight = parseInt(height);\n          if (maintainAspectRatio) {\n            newWidth = Math.round((newHeight / originalHeight) * originalWidth);\n          } else {\n            newWidth = parseInt(width);\n          }\n        }\n      } else {\n        // percentage\n        const scale = parseInt(percentage) / 100;\n        newWidth = Math.round(originalWidth * scale);\n        newHeight = Math.round(originalHeight * scale);\n      }\n\n      // Update SVG attributes\n      svgElement.setAttribute('width', String(newWidth));\n      svgElement.setAttribute('height', String(newHeight));\n\n      // If viewBox isn't already set, add it to preserve scaling\n      if (!viewBox) {\n        svgElement.setAttribute(\n          'viewBox',\n          `0 0 ${originalWidth} ${originalHeight}`\n        );\n      }\n\n      // Serialize the modified SVG document\n      const serializer = new XMLSerializer();\n      const svgString = serializer.serializeToString(svgDoc);\n\n      // Create a new file\n      return new File([svgString], file.name, {\n        type: 'image/svg+xml'\n      });\n    } catch (error) {\n      console.error('Error processing SVG:', error);\n      // Fall back to canvas method if SVG processing fails\n    }\n  } else if (file.type === 'image/gif') {\n    try {\n      const ffmpeg = new FFmpeg();\n\n      await ffmpeg.load({\n        wasmURL:\n          'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n      });\n\n      // Write the input file to memory\n      await ffmpeg.writeFile('input.gif', await fetchFile(file));\n\n      // Calculate new dimensions\n      let newWidth = 0;\n      let newHeight = 0;\n      let scaleFilter = '';\n\n      if (resizeMethod === 'pixels') {\n        if (dimensionType === 'width') {\n          newWidth = parseInt(width);\n          if (maintainAspectRatio) {\n            scaleFilter = `scale=${newWidth}:-1`;\n          } else {\n            newHeight = parseInt(height);\n            scaleFilter = `scale=${newWidth}:${newHeight}`;\n          }\n        } else {\n          // height\n          newHeight = parseInt(height);\n          if (maintainAspectRatio) {\n            scaleFilter = `scale=-1:${newHeight}`;\n          } else {\n            newWidth = parseInt(width);\n            scaleFilter = `scale=${newWidth}:${newHeight}`;\n          }\n        }\n      } else {\n        // percentage\n        const scale = parseInt(percentage) / 100;\n        scaleFilter = `scale=iw*${scale}:ih*${scale}`;\n      }\n\n      // Run FFmpeg command\n      await ffmpeg.exec(['-i', 'input.gif', '-vf', scaleFilter, 'output.gif']);\n\n      // Read the output file\n      const data = await ffmpeg.readFile('output.gif');\n\n      // Create a new File object\n      return new File([data as any], file.name, { type: 'image/gif' });\n    } catch (error) {\n      console.error('Error processing GIF with FFmpeg:', error);\n      // Fall back to canvas method if FFmpeg processing fails\n    }\n  }\n  // Create canvas\n  const canvas = document.createElement('canvas');\n  const ctx = canvas.getContext('2d');\n  if (ctx == null) return null;\n\n  // Load image\n  const img = new Image();\n  img.src = URL.createObjectURL(file);\n  await img.decode();\n\n  // Calculate new dimensions\n  let newWidth = img.width;\n  let newHeight = img.height;\n\n  if (resizeMethod === 'pixels') {\n    if (dimensionType === 'width') {\n      newWidth = parseInt(width);\n      if (maintainAspectRatio) {\n        newHeight = Math.round((newWidth / img.width) * img.height);\n      } else {\n        newHeight = parseInt(height);\n      }\n    } else {\n      // height\n      newHeight = parseInt(height);\n      if (maintainAspectRatio) {\n        newWidth = Math.round((newHeight / img.height) * img.width);\n      } else {\n        newWidth = parseInt(width);\n      }\n    }\n  } else {\n    // percentage\n    const scale = parseInt(percentage) / 100;\n    newWidth = Math.round(img.width * scale);\n    newHeight = Math.round(img.height * scale);\n  }\n\n  // Set canvas dimensions\n  canvas.width = newWidth;\n  canvas.height = newHeight;\n\n  // Draw resized image\n  ctx.drawImage(img, 0, 0, newWidth, newHeight);\n\n  // Determine output type based on input file\n  let outputType = 'image/png';\n  if (file.type) {\n    outputType = file.type;\n  }\n\n  // Convert canvas to blob and create file\n  return new Promise((resolve) => {\n    canvas.toBlob((blob) => {\n      if (blob) {\n        resolve(new File([blob], file.name, { type: outputType }));\n      } else {\n        resolve(null);\n      }\n    }, outputType);\n  });\n};\n"
  },
  {
    "path": "src/pages/tools/image/generic/resize/types.ts",
    "content": "export type InitialValuesType = {\n  resizeMethod: 'pixels' | 'percentage';\n  dimensionType: 'width' | 'height';\n  width: string;\n  height: string;\n  percentage: string;\n  maintainAspectRatio: boolean;\n};\n"
  },
  {
    "path": "src/pages/tools/image/generic/rotate/index.tsx",
    "content": "import { ToolComponentProps } from '@tools/defineTool';\nimport { InitialValuesType } from './type';\nimport * as Yup from 'yup';\n\nimport { useState } from 'react';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport { Box } from '@mui/material';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport ToolContent from '@components/ToolContent';\nimport ToolImageInput from '@components/input/ToolImageInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport { processImage } from './service';\n\nconst initialValues: InitialValuesType = {\n  rotateAngle: '90',\n  rotateMethod: 'Preset'\n};\n\nconst validationSchema = Yup.object({\n  rotateAngle: Yup.number().when('rotateMethod', {\n    is: 'degrees',\n    then: (schema) =>\n      schema\n        .min(-360, 'Rotate angle must be at least -360')\n        .max(360, 'Rotate angle must be at most 360')\n        .required('Rotate angle is required')\n  })\n});\n\nexport default function RotateImage({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n\n  const compute = async (optionsValues: InitialValuesType, input: any) => {\n    if (!input) return;\n    setResult(await processImage(input, optionsValues));\n  };\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Rotate Method',\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('rotateMethod', 'Preset')}\n            checked={values.rotateMethod === 'Preset'}\n            description={'Rotate by a specific angle in degrees.'}\n            title={'Preset angle'}\n          />\n          <SimpleRadio\n            onClick={() => updateField('rotateMethod', 'Custom')}\n            checked={values.rotateMethod === 'Custom'}\n            description={'Rotate by a custom angle in degrees.'}\n            title={'Custom angle'}\n          />\n        </Box>\n      )\n    },\n    ...(values.rotateMethod === 'Preset'\n      ? [\n          {\n            title: 'Preset angle',\n            component: (\n              <Box>\n                <SelectWithDesc\n                  selected={values.rotateAngle}\n                  onChange={(val) => updateField('rotateAngle', val)}\n                  description={'Rotate by a specific angle in degrees.'}\n                  options={[\n                    { label: '90 degrees', value: '90' },\n                    { label: '180 degrees', value: '180' },\n                    { label: '270 degrees', value: '270' }\n                  ]}\n                />\n              </Box>\n            )\n          }\n        ]\n      : [\n          {\n            title: 'Custom angle',\n            component: (\n              <Box>\n                <TextFieldWithDesc\n                  value={values.rotateAngle}\n                  onOwnChange={(val) => updateField('rotateAngle', val)}\n                  description={\n                    'Rotate by a custom angle in degrees(from -360 to 360).'\n                  }\n                  inputProps={{\n                    type: 'number',\n                    min: -360,\n                    max: 360\n                  }}\n                />\n              </Box>\n            )\n          }\n        ])\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      validationSchema={validationSchema}\n      inputComponent={\n        <ToolImageInput\n          value={input}\n          onChange={setInput}\n          title={'Input Image'}\n          accept={['image/*']}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          value={result}\n          title={'Rotated Image'}\n          extension={input?.name.split('.').pop() || 'png'}\n        />\n      }\n      toolInfo={{\n        title: 'Rotate Image',\n        description:\n          'This tool allows you to rotate images by a specific angle in any degrees.'\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/generic/rotate/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('image-generic', {\n  i18n: {\n    name: 'image:rotate.title',\n    description: 'image:rotate.description',\n    shortDescription: 'image:rotate.shortDescription'\n  },\n\n  path: 'rotate',\n  icon: 'mdi:rotate-clockwise',\n\n  keywords: ['rotate', 'image', 'angle', 'jpg', 'png'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/generic/rotate/service.ts",
    "content": "import { InitialValuesType } from './type';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\n\nexport const processImage = async (\n  file: File,\n  options: InitialValuesType\n): Promise<File | null> => {\n  const { rotateAngle, rotateMethod } = options;\n  if (file.type === 'image/svg+xml') {\n    try {\n      // Read the SVG file\n      const fileText = await file.text();\n      const parser = new DOMParser();\n      const svgDoc = parser.parseFromString(fileText, 'image/svg+xml');\n      const svgElement = svgDoc.documentElement as unknown as SVGSVGElement;\n\n      // Get current transform attribute or create new one\n      let currentTransform = svgElement.getAttribute('transform') || '';\n\n      const angle = parseInt(rotateAngle);\n\n      // Add rotation if needed\n      if (angle !== 0) {\n        // Get SVG dimensions\n        const bbox = svgElement.getBBox();\n        const centerX = bbox.x + bbox.width / 2;\n        const centerY = bbox.y + bbox.height / 2;\n\n        currentTransform += ` rotate(${angle} ${centerX} ${centerY})`;\n      }\n\n      // Apply transform\n      svgElement.setAttribute('transform', currentTransform.trim());\n\n      // Convert back to file\n      const serializer = new XMLSerializer();\n      const svgString = serializer.serializeToString(svgDoc);\n      const blob = new Blob([svgString], { type: 'image/svg+xml' });\n      return new File([blob], file.name, { type: 'image/svg+xml' });\n    } catch (error) {\n      console.error('Error processing SVG:', error);\n      return null;\n    }\n  }\n\n  // For non-SVG images, use FFmpeg\n  try {\n    const ffmpeg = new FFmpeg();\n    await ffmpeg.load();\n\n    // Write input file\n    await ffmpeg.writeFile('input', await fetchFile(file));\n\n    // Determine rotation command\n    const rotateCmd = `rotate=${rotateAngle}*PI/180`;\n\n    // Execute FFmpeg command\n    await ffmpeg.exec([\n      '-i',\n      'input',\n      '-vf',\n      rotateCmd,\n      'output.' + file.name.split('.').pop()\n    ]);\n\n    // Read the output file\n    const data = await ffmpeg.readFile('output.' + file.name.split('.').pop());\n    return new File([data as any], file.name, { type: file.type });\n  } catch (error) {\n    console.error('Error processing image:', error);\n    return null;\n  }\n};\n"
  },
  {
    "path": "src/pages/tools/image/generic/rotate/type.ts",
    "content": "export type InitialValuesType = {\n  rotateAngle: string; // the angle to rotate the image\n  rotateMethod: 'Preset' | 'Custom';\n};\n"
  },
  {
    "path": "src/pages/tools/image/index.ts",
    "content": "import { pngTools } from './png';\nimport { imageGenericTools } from './generic';\n\nexport const imageTools = [...imageGenericTools, ...pngTools];\n"
  },
  {
    "path": "src/pages/tools/image/png/compress-png/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolImageInput from '@components/input/ToolImageInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport TextFieldWithDesc from 'components/options/TextFieldWithDesc';\nimport imageCompression from 'browser-image-compression';\nimport Typography from '@mui/material/Typography';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\n\nconst initialValues = {\n  rate: '50'\n};\nconst validationSchema = Yup.object({\n  // splitSeparator: Yup.string().required('The separator is required')\n});\n\nexport default function ChangeColorsInPng({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [originalSize, setOriginalSize] = useState<number | null>(null); // Store original file size\n  const [compressedSize, setCompressedSize] = useState<number | null>(null); // Store compressed file size\n\n  const compressImage = async (file: File, rate: number) => {\n    if (!file) return;\n\n    // Set original file size\n    setOriginalSize(file.size);\n\n    const options = {\n      maxSizeMB: 1, // Maximum size in MB\n      maxWidthOrHeight: 1024, // Maximum width or height\n      quality: rate / 100, // Convert percentage to decimal (e.g., 50% becomes 0.5)\n      useWebWorker: true\n    };\n\n    try {\n      const compressedFile = await imageCompression(file, options);\n      setResult(compressedFile);\n      setCompressedSize(compressedFile.size); // Set compressed file size\n    } catch (error) {\n      console.error('Error during compression:', error);\n    }\n  };\n\n  const compute = (optionsValues: typeof initialValues, input: any) => {\n    if (!input) return;\n\n    const { rate } = optionsValues;\n    compressImage(input, Number(rate)); // Pass the rate as a number\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolImageInput\n          value={input}\n          onChange={setInput}\n          accept={['image/png']}\n          title={'Input PNG'}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          title={'Compressed PNG'}\n          value={result}\n          extension={'png'}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: 'Compression options',\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                value={values.rate}\n                onOwnChange={(val) => updateField('rate', val)}\n                description={'Compression rate (1-100)'}\n              />\n            </Box>\n          )\n        },\n        {\n          title: 'File sizes',\n          component: (\n            <Box>\n              <Box>\n                {originalSize !== null && (\n                  <Typography>\n                    Original Size: {(originalSize / 1024).toFixed(2)} KB\n                  </Typography>\n                )}\n                {compressedSize !== null && (\n                  <Typography>\n                    Compressed Size: {(compressedSize / 1024).toFixed(2)} KB\n                  </Typography>\n                )}\n              </Box>\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n      setInput={setInput}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/png/compress-png/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('png', {\n  i18n: {\n    name: 'image:compressPng.title',\n    description: 'image:compressPng.description',\n    shortDescription: 'image:compressPng.shortDescription'\n  },\n\n  path: 'compress-png',\n  icon: 'material-symbols-light:compress',\n\n  keywords: ['compress', 'png'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/png/compress-png/service.ts",
    "content": ""
  },
  {
    "path": "src/pages/tools/image/png/convert-jgp-to-png/convert-jgp-to-png.e2e.spec.ts",
    "content": "import { expect, test } from '@playwright/test';\nimport { Buffer } from 'buffer';\nimport path from 'path';\nimport Jimp from 'jimp';\n\ntest.describe('Convert JPG to PNG tool', () => {\n  test.beforeEach(async ({ page }) => {\n    await page.goto('/png/convert-jgp-to-png');\n  });\n\n  test('should convert jpg to png', async ({ page }) => {\n    // Upload image\n    const fileInput = page.locator('input[type=\"file\"]');\n    const imagePath = path.join(__dirname, 'test.jpg');\n    await fileInput?.setInputFiles(imagePath);\n\n    // Click on download\n    const downloadPromise = page.waitForEvent('download');\n    await page.getByText('Download').click();\n\n    // Intercept and read downloaded PNG\n    const download = await downloadPromise;\n    const downloadStream = await download.createReadStream();\n\n    const chunks = [];\n    for await (const chunk of downloadStream) {\n      chunks.push(chunk);\n    }\n    const fileContent = Buffer.concat(chunks);\n\n    expect(fileContent.length).toBeGreaterThan(0);\n\n    // Check that the first pixel is 0x808080ff\n    const image = await Jimp.read(fileContent);\n    const color = image.getPixelColor(0, 0);\n    expect(color).toBe(0x808080ff);\n  });\n\n  test('should apply transparency before converting jpg to png', async ({\n    page\n  }) => {\n    // Upload image\n    const fileInput = page.locator('input[type=\"file\"]');\n    const imagePath = path.join(__dirname, 'test.jpg');\n    await fileInput?.setInputFiles(imagePath);\n\n    // Enable transparency on color 0x808080\n    await page.getByLabel('Enable PNG Transparency').check();\n    await page.getByTestId('color-input').fill('#808080');\n\n    // Click on download\n    const downloadPromise = page.waitForEvent('download');\n    await page.getByText('Download').click();\n\n    // Intercept and read downloaded PNG\n    const download = await downloadPromise;\n    const downloadStream = await download.createReadStream();\n\n    const chunks = [];\n    for await (const chunk of downloadStream) {\n      chunks.push(chunk);\n    }\n    const fileContent = Buffer.concat(chunks);\n\n    expect(fileContent.length).toBeGreaterThan(0);\n\n    // Check that the first pixel is transparent\n    const image = await Jimp.read(fileContent);\n    const color = image.getPixelColor(0, 0);\n    expect(color).toBe(0);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/image/png/convert-jgp-to-png/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport ToolImageInput from 'components/input/ToolImageInput';\nimport CheckboxWithDesc from 'components/options/CheckboxWithDesc';\nimport ColorSelector from 'components/options/ColorSelector';\nimport TextFieldWithDesc from 'components/options/TextFieldWithDesc';\nimport ToolFileResult from 'components/result/ToolFileResult';\nimport Color from 'color';\nimport React, { useState } from 'react';\nimport { areColorsSimilar } from 'utils/color';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\n\nconst initialValues = {\n  enableTransparency: false,\n  color: 'white',\n  similarity: '10'\n};\n\nexport default function ConvertJgpToPng({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n\n  const compute = async (\n    optionsValues: typeof initialValues,\n    input: any\n  ): Promise<void> => {\n    if (!input) return;\n\n    const processImage = async (\n      file: File,\n      transparencyTransform?: {\n        color: [number, number, number];\n        similarity: number;\n      }\n    ) => {\n      const canvas = document.createElement('canvas');\n      const ctx = canvas.getContext('2d');\n      if (ctx == null) return;\n      const img = new Image();\n\n      img.src = URL.createObjectURL(file);\n      await img.decode();\n\n      canvas.width = img.width;\n      canvas.height = img.height;\n      ctx.drawImage(img, 0, 0);\n\n      if (transparencyTransform) {\n        const { color, similarity } = transparencyTransform;\n\n        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\n        const data: Uint8ClampedArray = imageData.data;\n\n        for (let i = 0; i < data.length; i += 4) {\n          const currentColor: [number, number, number] = [\n            data[i],\n            data[i + 1],\n            data[i + 2]\n          ];\n          if (areColorsSimilar(currentColor, color, similarity)) {\n            data[i + 3] = 0;\n          }\n        }\n\n        ctx.putImageData(imageData, 0, 0);\n      }\n\n      canvas.toBlob((blob) => {\n        if (blob) {\n          const newFile = new File([blob], file.name, {\n            type: 'image/png'\n          });\n          setResult(newFile);\n        }\n      }, 'image/png');\n    };\n\n    if (optionsValues.enableTransparency) {\n      let rgb: [number, number, number];\n      try {\n        //@ts-ignore\n        rgb = Color(optionsValues.color).rgb().array();\n      } catch (err) {\n        return;\n      }\n\n      processImage(input, {\n        color: rgb,\n        similarity: Number(optionsValues.similarity)\n      });\n    } else {\n      processImage(input);\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolImageInput\n          value={input}\n          onChange={setInput}\n          accept={['image/jpeg']}\n          title={'Input JPG'}\n        />\n      }\n      resultComponent={\n        <ToolFileResult title={'Output PNG'} value={result} extension={'png'} />\n      }\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: 'PNG Transparency Color',\n          component: (\n            <Box>\n              <CheckboxWithDesc\n                key=\"enableTransparency\"\n                title=\"Enable PNG Transparency\"\n                checked={!!values.enableTransparency}\n                onChange={(value) => updateField('enableTransparency', value)}\n                description=\"Make the color below transparent.\"\n              />\n              <ColorSelector\n                value={values.color}\n                onColorChange={(val) => updateField('color', val)}\n                description={'With this color (to color)'}\n                inputProps={{ 'data-testid': 'color-input' }}\n              />\n              <TextFieldWithDesc\n                value={values.similarity}\n                onOwnChange={(val) => updateField('similarity', val)}\n                description={\n                  'Match this % of similar. For example, 10% white will match white and a little bit of gray.'\n                }\n              />\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n      setInput={setInput}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/image/png/convert-jgp-to-png/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('png', {\n  i18n: {\n    name: 'image:convertJgpToPng.title',\n    description: 'image:convertJgpToPng.description',\n    shortDescription: 'image:convertJgpToPng.shortDescription'\n  },\n\n  path: 'convert-jgp-to-png',\n  icon: 'ph:file-jpg-thin',\n\n  keywords: ['convert', 'jgp', 'png'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/image/png/index.ts",
    "content": "import { tool as pngCompressPng } from './compress-png/meta';\nimport { tool as convertJgpToPng } from './convert-jgp-to-png/meta';\n\nexport const pngTools = [pngCompressPng, convertJgpToPng];\n"
  },
  {
    "path": "src/pages/tools/json/escape-json/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolCodeInput from '@components/input/ToolCodeInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { escapeJson } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { Box } from '@mui/material';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\n\nconst initialValues = {\n  wrapInQuotesFlag: false\n};\n\ntype InitialValuesType = typeof initialValues;\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Escape a Simple JSON Object',\n    description: `In this example, we escape all quotes (\") around the keys and values in a simple JSON object. This ensures that the JSON data is interpreted correctly if it's used in another JSON object or assigned to a variable as a string.`,\n    sampleText: `{\"country\": \"Spain\", \"capital\": \"Madrid\"}`,\n    sampleResult: `{{\\\\\"country\\\\\": \\\\\"Spain\\\\\", \\\\\"capital\\\\\": \\\\\"Madrid\\\\\"}`,\n    sampleOptions: {\n      wrapInQuotesFlag: false\n    }\n  },\n  {\n    title: 'Escape a Complex JSON Object',\n    description: `In this example, we escape a more complex JSON object with nested elements containing data about the Margherita pizza recipe. We escape all quotes within the object as well as convert all line breaks into special \"\\n\" characters. Additionally, we wrap the entire output in double quotes by enabling the \"Wrap Output in Quotes\" option.`,\n    sampleText: `{\n  \"name\": \"Pizza Margherita\",\n  \"ingredients\": [\n    \"tomato sauce\",\n    \"mozzarella cheese\",\n    \"fresh basil\"\n  ],\n  \"price\": 12.50,\n  \"vegetarian\": true\n}`,\n    sampleResult: `\"{\\\\n  \\\\\"name\\\\\": \\\\\"Pizza Margherita\\\\\",\\\\n  \\\\\"ingredients\\\\\": [\\\\n\\\\\"tomato sauce\\\\\",\\\\n    \\\\\"mozzarella cheese\\\\\",\\\\n    \\\\\"fresh basil\\\\\"\\\\n  ],\\\\n  \\\\\"price\\\\\": 12.50,\\\\n  \\\\\"vegetarian\\\\\": true\\\\n}\"`,\n    sampleOptions: {\n      wrapInQuotesFlag: true\n    }\n  },\n  {\n    title: 'Escape a JSON Array of Numbers',\n    description: `This example showcases that escaping isn't necessary for JSON arrays containing only numbers. Since numbers themselves don't hold special meaning in JSON, the tool doesn't modify the input and the output remains the same as the original JSON array.`,\n    sampleText: `[1, 2, 3]`,\n    sampleResult: `[1, 2, 3]`,\n    sampleOptions: {\n      wrapInQuotesFlag: false\n    }\n  }\n];\n\nexport default function EscapeJsonTool({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (options: InitialValuesType, input: string) => {\n    setResult(escapeJson(input, options.wrapInQuotesFlag));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Quote Output',\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            onChange={(val) => updateField('wrapInQuotesFlag', val)}\n            checked={values.wrapInQuotesFlag}\n            title={'Wrap Output In Quotes'}\n            description={'Add double quotes around the output JSON data.'}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolCodeInput\n          title=\"Input JSON\"\n          value={input}\n          onChange={setInput}\n          language=\"json\"\n        />\n      }\n      resultComponent={\n        <ToolTextResult\n          title=\"Escaped JSON\"\n          value={result}\n          keepSpecialCharacters\n          extension={'json'}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      toolInfo={{\n        title: 'What is a JSON Escaper?',\n        description: longDescription\n      }}\n      exampleCards={exampleCards}\n      input={input}\n      setInput={setInput}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/json/escape-json/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('json', {\n  path: 'escape-json',\n  icon: 'material-symbols:code',\n\n  keywords: ['json', 'escape', 'characters', 'format'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'json:escapeJson.title',\n    description: 'json:escapeJson.description',\n    shortDescription: 'json:escapeJson.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/json/escape-json/service.ts",
    "content": "export const escapeJson = (input: string, wrapInQuotesFlag: boolean) => {\n  const escapedJson = JSON.stringify(input);\n  if (!wrapInQuotesFlag) {\n    return escapedJson.slice(1, -1);\n  }\n  return escapedJson;\n};\n"
  },
  {
    "path": "src/pages/tools/json/index.ts",
    "content": "import { tool as jsonPrettify } from './prettify/meta';\nimport { tool as jsonMinify } from './minify/meta';\nimport { tool as jsonStringify } from './stringify/meta';\nimport { tool as validateJson } from './validateJson/meta';\nimport { tool as jsonToXml } from './json-to-xml/meta';\nimport { tool as escapeJson } from './escape-json/meta';\nimport { tool as jsonComparison } from './json-comparison/meta';\nimport { tool as jsonToCsv } from './json-to-csv/meta';\n\nexport const jsonTools = [\n  validateJson,\n  jsonPrettify,\n  jsonMinify,\n  jsonStringify,\n  jsonToXml,\n  jsonToCsv,\n  escapeJson,\n  jsonComparison\n];\n"
  },
  {
    "path": "src/pages/tools/json/json-comparison/index.tsx",
    "content": "import { useEffect, useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolCodeInput from '@components/input/ToolCodeInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { compareJson } from './service';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { Grid } from '@mui/material';\n\ntype InitialValuesType = {};\n\nconst initialValues: InitialValuesType = {};\n\nexport default function JsonComparison({ title }: ToolComponentProps) {\n  const [input1, setInput1] = useState<string>('');\n  const [input2, setInput2] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  useEffect(() => {\n    const compareInputs = () => {\n      try {\n        // Only compare if at least one input has content\n        if (input1.trim() || input2.trim()) {\n          const differences = compareJson(\n            input1 || '{}',\n            input2 || '{}',\n            'text'\n          );\n          setResult(differences);\n        } else {\n          setResult('');\n        }\n      } catch (error) {\n        setResult(\n          `Error: ${\n            error instanceof Error ? error.message : 'Invalid JSON format'\n          }`\n        );\n      }\n    };\n\n    compareInputs();\n  }, [input1, input2]);\n\n  const handleInput1Change = (value: string | undefined) => {\n    setInput1(value ?? '');\n  };\n\n  const handleInput2Change = (value: string) => {\n    setInput2(value);\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input1}\n      setInput={setInput1}\n      initialValues={initialValues}\n      getGroups={null}\n      compute={() => {}}\n      inputComponent={\n        <Grid container spacing={2}>\n          <Grid item xs={12} md={6} lg={4}>\n            <ToolCodeInput\n              title=\"First JSON\"\n              value={input1}\n              onChange={handleInput1Change}\n              language={'json'}\n            />\n          </Grid>\n          <Grid item xs={12} md={6} lg={4}>\n            <ToolCodeInput\n              title=\"Second JSON\"\n              language={'json'}\n              value={input2}\n              onChange={handleInput2Change}\n            />\n          </Grid>\n          <Grid item xs={12} md={12} lg={4}>\n            <ToolTextResult\n              title=\"Differences\"\n              value={result}\n              extension={'txt'}\n            />\n          </Grid>\n        </Grid>\n      }\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/json/json-comparison/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('json', {\n  path: 'json-comparison',\n  icon: 'fluent:branch-compare-24-regular',\n  keywords: ['json', 'compare', 'diff', 'differences', 'match', 'validation'],\n  component: lazy(() => import('./index')),\n\n  i18n: {\n    name: 'json:comparison.title',\n    description: 'json:comparison.description',\n    shortDescription: 'json:comparison.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/json/json-comparison/service.test.ts",
    "content": "import { compareJson } from './service';\n\ndescribe('compareJson', () => {\n  it('should identify missing properties', () => {\n    const json1 = '{\"name\": \"John\", \"age\": 30}';\n    const json2 = '{\"name\": \"John\"}';\n\n    expect(compareJson(json1, json2, 'text')).toContain(\n      'age: Missing in second JSON'\n    );\n  });\n\n  it('should identify value mismatches', () => {\n    const json1 = '{\"name\": \"John\", \"age\": 30}';\n    const json2 = '{\"name\": \"John\", \"age\": 25}';\n\n    expect(compareJson(json1, json2, 'text')).toContain(\n      'age: Mismatch: 30 != 25'\n    );\n  });\n\n  it('should handle nested objects', () => {\n    const json1 = '{\"person\": {\"name\": \"John\", \"age\": 30}}';\n    const json2 = '{\"person\": {\"name\": \"Jane\", \"age\": 30}}';\n\n    expect(compareJson(json1, json2, 'text')).toContain(\n      'person.name: Mismatch: John != Jane'\n    );\n  });\n\n  it('should return JSON format when specified', () => {\n    const json1 = '{\"name\": \"John\", \"age\": 30}';\n    const json2 = '{\"name\": \"Jane\", \"age\": 25}';\n\n    const result = compareJson(json1, json2, 'json');\n    const parsed = JSON.parse(result);\n\n    expect(parsed).toHaveProperty('name');\n    expect(parsed).toHaveProperty('age');\n  });\n\n  it('should handle arrays', () => {\n    const json1 = '{\"numbers\": [1, 2, 3]}';\n    const json2 = '{\"numbers\": [1, 2, 4]}';\n\n    expect(compareJson(json1, json2, 'text')).toContain(\n      'numbers.2: Mismatch: 3 != 4'\n    );\n  });\n\n  it('should return \"No differences found\" for identical JSONs', () => {\n    const json1 = '{\"name\": \"John\", \"age\": 30}';\n    const json2 = '{\"name\": \"John\", \"age\": 30}';\n\n    expect(compareJson(json1, json2, 'text')).toBe('No differences found');\n  });\n\n  it('should throw error for invalid JSON', () => {\n    const json1 = '{\"name\": \"John\"';\n    const json2 = '{\"name\": \"John\"}';\n\n    expect(() => compareJson(json1, json2, 'text')).toThrow();\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/json/json-comparison/service.ts",
    "content": "const fixTrailingCommas = (json: string): string => {\n  // Replace trailing commas in objects and arrays with proper JSON syntax\n  return json\n    .replace(/,\\s*([}\\]])/g, '$1') // Remove trailing commas in objects and arrays\n    .replace(/,\\s*\\n\\s*([}\\]])/g, '\\n$1'); // Also handle when the closing bracket is on a new line\n};\n\nconst tryParseJSON = (\n  json: string\n): { valid: boolean; data?: any; error?: string } => {\n  if (!json.trim()) {\n    return { valid: true, data: {} };\n  }\n\n  try {\n    // Try to parse after fixing trailing commas\n    const fixedJson = fixTrailingCommas(json);\n    const data = JSON.parse(fixedJson);\n    return { valid: true, data };\n  } catch (error) {\n    const errorMessage =\n      error instanceof SyntaxError ? error.message : 'Invalid JSON format';\n    // Extract line and column info from the error message if available\n    const match = errorMessage.match(/at line (\\d+) column (\\d+)/);\n    if (match) {\n      const [, line, column] = match;\n      return {\n        valid: false,\n        error: `${errorMessage}\\nLocation: Line ${line}, Column ${column}`\n      };\n    }\n    return {\n      valid: false,\n      error: errorMessage\n    };\n  }\n};\n\nexport const compareJson = (\n  json1: string,\n  json2: string,\n  format: 'text' | 'json'\n): string => {\n  // Handle empty inputs\n  if (!json1.trim() && !json2.trim()) return '';\n\n  // Parse both JSON inputs\n  const parsed1 = tryParseJSON(json1);\n  const parsed2 = tryParseJSON(json2);\n\n  // Handle parsing errors\n  if (!parsed1.valid || !parsed2.valid) {\n    const errors = [];\n    if (!parsed1.valid) {\n      errors.push(`First JSON: ${parsed1.error}`);\n    }\n    if (!parsed2.valid) {\n      errors.push(`Second JSON: ${parsed2.error}`);\n    }\n    throw new Error(errors.join('\\n\\n'));\n  }\n\n  // Compare the valid JSON objects\n  if (format === 'json') {\n    const diffs = findDifferencesJSON(parsed1.data, parsed2.data);\n    return JSON.stringify(diffs);\n  } else {\n    const differences = findDifferencesText(parsed1.data, parsed2.data);\n    if (differences.length === 0) {\n      return 'No differences found';\n    }\n    return differences.join('\\n');\n  }\n};\n\nconst findDifferencesText = (\n  obj1: any,\n  obj2: any,\n  path: string[] = []\n): string[] => {\n  const differences: string[] = [];\n  const processPath = (p: string[]): string =>\n    p.length ? p.join('.') : 'root';\n\n  // Compare all keys in obj1\n  for (const key in obj1) {\n    const currentPath = [...path, key];\n\n    if (!(key in obj2)) {\n      differences.push(`${processPath(currentPath)}: Missing in second JSON`);\n      continue;\n    }\n\n    const value1 = obj1[key];\n    const value2 = obj2[key];\n\n    if (\n      typeof value1 === 'object' &&\n      value1 !== null &&\n      typeof value2 === 'object' &&\n      value2 !== null\n    ) {\n      differences.push(...findDifferencesText(value1, value2, currentPath));\n    } else if (value1 !== value2) {\n      differences.push(\n        `${processPath(currentPath)}: Mismatch: ${value1} != ${value2}`\n      );\n    }\n  }\n\n  // Check for keys in obj2 that don't exist in obj1\n  for (const key in obj2) {\n    if (!(key in obj1)) {\n      const currentPath = [...path, key];\n      differences.push(`${processPath(currentPath)}: Missing in first JSON`);\n    }\n  }\n\n  return differences;\n};\n\nconst findDifferencesJSON = (obj1: any, obj2: any): Record<string, string> => {\n  const result: Record<string, string> = {};\n\n  // Compare all properties\n  const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);\n\n  for (const key of allKeys) {\n    if (!(key in obj1)) {\n      result[key] = 'Missing in first JSON';\n    } else if (!(key in obj2)) {\n      result[key] = 'Missing in second JSON';\n    } else if (obj1[key] !== obj2[key]) {\n      result[key] = `Mismatch: ${obj1[key]} != ${obj2[key]}`;\n    }\n  }\n\n  return result;\n};\n"
  },
  {
    "path": "src/pages/tools/json/json-to-csv/index.tsx",
    "content": "import { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolCodeInput from '@components/input/ToolCodeInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { convertJsonToCsv } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { Box } from '@mui/material';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport { InitialValuesType } from './types';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  delimiter: ',',\n  includeHeaders: true,\n  quoteStrings: 'auto'\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Array of objects',\n    description:\n      'Convert multiple JSON objects into CSV rows, one row per object.',\n    sampleText: `[\n  { \"name\": \"John Doe\", \"age\": 25, \"city\": \"New York\" },\n  { \"name\": \"Jane Doe\", \"age\": 30, \"city\": \"Los Angeles\" },\n  { \"name\": \"Bob Smith\", \"age\": 22, \"city\": \"Chicago\" }\n]`,\n    sampleResult: `name,age,city\\nJohn Doe,25,New York\\nJane Doe,30,Los Angeles\\nBob Smith,22,Chicago`,\n    sampleOptions: {\n      ...initialValues\n    }\n  },\n  {\n    title: 'Nested object (dot notation)',\n    description:\n      'Nested keys are flattened using dot notation (e.g. address.city).',\n    sampleText: `[\n  {\n    \"name\": \"John Doe\",\n    \"age\": 25,\n    \"address\": {\n      \"street\": \"123 Main St\",\n      \"city\": \"New York\",\n      \"state\": \"NY\",\n      \"postalCode\": \"10001\"\n    },\n    \"hobbies\": [\"reading\", \"running\"]\n  }\n]`,\n    sampleResult: `name,age,address.street,address.city,address.state,address.postalCode,hobbies[0],hobbies[1]\\nJohn Doe,25,123 Main St,New York,NY,10001,reading,running`,\n    sampleOptions: {\n      ...initialValues\n    }\n  },\n  {\n    title: 'Sparse rows',\n    description:\n      'Missing keys are filled with empty values to keep columns aligned.',\n    sampleText: `[\n  { \"name\": \"Alice\", \"age\": 30 },\n  { \"name\": \"Bob\", \"city\": \"Paris\" },\n  { \"name\": \"Carol\", \"age\": 25, \"city\": \"Rome\" }\n]`,\n    sampleResult: `name,age,city\\nAlice,30,\\nBob,,Paris\\nCarol,25,Rome`,\n    sampleOptions: {\n      ...initialValues\n    }\n  }\n];\n\nexport default function JsonToCsv({ title }: ToolComponentProps) {\n  const { t } = useTranslation('json');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    if (input) {\n      try {\n        const csvResult = convertJsonToCsv(input, values);\n        setResult(csvResult);\n      } catch (error) {\n        setResult(\n          `Error: ${\n            error instanceof Error ? error.message : 'Invalid JSON format'\n          }`\n        );\n      }\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={initialValues}\n      compute={compute}\n      exampleCards={exampleCards}\n      inputComponent={\n        <ToolCodeInput\n          title={t('jsonToCsv.inputTitle')}\n          value={input}\n          onChange={setInput}\n          language=\"json\"\n        />\n      }\n      resultComponent={\n        <ToolTextResult\n          title={t('jsonToCsv.outputTitle')}\n          value={result}\n          extension={'csv'}\n        />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('jsonToCsv.delimiterOption'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                description={t('jsonToCsv.options.delimiter')}\n                value={values.delimiter}\n                onOwnChange={(val) => updateField('delimiter', val)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('jsonToCsv.quotingOption'),\n          component: (\n            <Box>\n              <SimpleRadio\n                checked={values.quoteStrings === 'auto'}\n                title={t('jsonToCsv.options.autoQuote.label')}\n                description={t('jsonToCsv.options.autoQuote.description')}\n                onClick={() => updateField('quoteStrings', 'auto')}\n              />\n              <SimpleRadio\n                checked={values.quoteStrings === 'always'}\n                title={t('jsonToCsv.options.alwaysQuote.label')}\n                description={t('jsonToCsv.options.alwaysQuote.description')}\n                onClick={() => updateField('quoteStrings', 'always')}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('jsonToCsv.headerOption'),\n          component: (\n            <Box>\n              <CheckboxWithDesc\n                checked={values.includeHeaders}\n                onChange={(value) => updateField('includeHeaders', value)}\n                title={t('jsonToCsv.options.header.label')}\n                description={t('jsonToCsv.options.header.description')}\n              />\n            </Box>\n          )\n        }\n      ]}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/json/json-to-csv/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('json', {\n  path: 'json-to-csv',\n  icon: 'material-symbols:code',\n  keywords: [],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'json:jsonToCsv.title',\n    description: 'json:jsonToCsv.description',\n    shortDescription: 'json:jsonToCsv.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/json/json-to-csv/service.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { convertJsonToCsv } from './service';\n\nconst defaultOptions: Parameters<typeof convertJsonToCsv>[1] = {\n  delimiter: ',',\n  includeHeaders: true,\n  quoteStrings: 'auto'\n};\n\ndescribe('convertJsonToCsv', () => {\n  describe('basic conversion', () => {\n    it('converts a flat array of objects', () => {\n      const input = JSON.stringify([\n        { name: 'Alice', age: 30 },\n        { name: 'Bob', age: 25 }\n      ]);\n\n      expect(convertJsonToCsv(input, defaultOptions)).toBe(\n        `name,age\\r\\nAlice,30\\r\\nBob,25`\n      );\n    });\n\n    it('converts a single object', () => {\n      const input = JSON.stringify({ name: 'Alice', age: 30 });\n\n      expect(convertJsonToCsv(input, defaultOptions)).toBe(\n        `name,age\\r\\nAlice,30`\n      );\n    });\n\n    it('excludes header row when includeHeaders is false', () => {\n      const input = JSON.stringify([{ name: 'Alice', age: 30 }]);\n\n      expect(\n        convertJsonToCsv(input, { ...defaultOptions, includeHeaders: false })\n      ).toBe(`Alice,30`);\n    });\n  });\n\n  describe('flattening', () => {\n    it('flattens nested objects using dot notation', () => {\n      const input = JSON.stringify([\n        { name: 'Alice', address: { city: 'Paris', zip: '75000' } }\n      ]);\n\n      expect(convertJsonToCsv(input, defaultOptions)).toBe(\n        `name,address.city,address.zip\\r\\nAlice,Paris,75000`\n      );\n    });\n\n    it('flattens arrays using index notation', () => {\n      const input = JSON.stringify([\n        { name: 'Alice', tags: ['admin', 'user'] }\n      ]);\n\n      expect(convertJsonToCsv(input, defaultOptions)).toBe(\n        `name,tags[0],tags[1]\\r\\nAlice,admin,user`\n      );\n    });\n\n    it('flattens deeply nested structures', () => {\n      const input = JSON.stringify([\n        { user: { address: { geo: { lat: 48.8566, lng: 2.3522 } } } }\n      ]);\n\n      expect(convertJsonToCsv(input, defaultOptions)).toBe(\n        `user.address.geo.lat,user.address.geo.lng\\r\\n48.8566,2.3522`\n      );\n    });\n  });\n\n  describe('sparse rows', () => {\n    it('fills missing keys with empty values', () => {\n      const input = JSON.stringify([\n        { name: 'Alice', age: 30 },\n        { name: 'Bob', city: 'Paris' }\n      ]);\n\n      expect(convertJsonToCsv(input, defaultOptions)).toBe(\n        `name,age,city\\r\\nAlice,30,\\r\\nBob,,Paris`\n      );\n    });\n\n    it('filters out empty objects', () => {\n      const input = JSON.stringify([{}, { name: 'Alice' }]);\n\n      expect(convertJsonToCsv(input, defaultOptions)).toBe(`name\\r\\nAlice`);\n    });\n  });\n\n  describe('quoting', () => {\n    it('quotes cells containing the delimiter', () => {\n      const input = JSON.stringify([{ name: 'Smith, John' }]);\n\n      expect(convertJsonToCsv(input, defaultOptions)).toBe(\n        `name\\r\\n\"Smith, John\"`\n      );\n    });\n\n    it('escapes double-quotes by doubling them', () => {\n      const input = JSON.stringify([{ name: 'He said \"hello\"' }]);\n\n      expect(convertJsonToCsv(input, defaultOptions)).toBe(\n        `name\\r\\n\"He said \"\"hello\"\"\"`\n      );\n    });\n\n    it('quotes all cells when quoteStrings is always', () => {\n      const input = JSON.stringify([{ name: 'Alice', age: 30 }]);\n\n      expect(\n        convertJsonToCsv(input, { ...defaultOptions, quoteStrings: 'always' })\n      ).toBe(`\"name\",\"age\"\\r\\n\"Alice\",\"30\"`);\n    });\n\n    it('quotes cells containing newlines', () => {\n      const input = JSON.stringify([{ notes: 'line1\\nline2' }]);\n\n      expect(convertJsonToCsv(input, defaultOptions)).toBe(\n        `notes\\r\\n\"line1\\nline2\"`\n      );\n    });\n  });\n\n  describe('delimiters', () => {\n    it('uses semicolon as delimiter', () => {\n      const input = JSON.stringify([{ name: 'Alice', age: 30 }]);\n\n      expect(\n        convertJsonToCsv(input, { ...defaultOptions, delimiter: ';' })\n      ).toBe(`name;age\\r\\nAlice;30`);\n    });\n\n    it('uses tab as delimiter', () => {\n      const input = JSON.stringify([{ name: 'Alice', age: 30 }]);\n\n      expect(\n        convertJsonToCsv(input, { ...defaultOptions, delimiter: '\\t' })\n      ).toBe(`name\\tage\\r\\nAlice\\t30`);\n    });\n  });\n\n  describe('null and undefined values', () => {\n    it('converts null values to empty strings', () => {\n      const input = JSON.stringify([{ name: 'Alice', age: null }]);\n\n      expect(convertJsonToCsv(input, defaultOptions)).toBe(\n        `name,age\\r\\nAlice,`\n      );\n    });\n  });\n\n  describe('errors', () => {\n    it('throws on invalid JSON', () => {\n      expect(() => convertJsonToCsv('invalid json', defaultOptions)).toThrow(\n        'Invalid JSON input.'\n      );\n    });\n\n    it('throws on bare primitive', () => {\n      expect(() => convertJsonToCsv('42', defaultOptions)).toThrow(\n        'JSON input must be an object or array of objects, not a bare primitive.'\n      );\n    });\n\n    it('throws when no data rows are found', () => {\n      expect(() =>\n        convertJsonToCsv(JSON.stringify([{}, {}]), defaultOptions)\n      ).toThrow('No data found in the provided JSON.');\n    });\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/json/json-to-csv/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { getJsonHeaders } from 'utils/json';\n\n/**\n * Recursively flattens any JSON value into a flat object.\n * Objects → dot notation\n * Arrays  → index notation\n */\nfunction flattenRecursive(\n  value: unknown,\n  prefix = '',\n  result: Record<string, string> = {}\n): Record<string, string> {\n  if (value === null || value === undefined) {\n    if (prefix) result[prefix] = '';\n    return result;\n  }\n\n  if (typeof value !== 'object') {\n    if (prefix) result[prefix] = String(value);\n    return result;\n  }\n\n  if (Array.isArray(value)) {\n    value.forEach((item, index) => {\n      const newKey = prefix ? `${prefix}[${index}]` : `[${index}]`;\n      flattenRecursive(item, newKey, result);\n    });\n    return result;\n  }\n\n  for (const [key, val] of Object.entries(value)) {\n    const newKey = prefix ? `${prefix}.${key}` : key;\n    flattenRecursive(val, newKey, result);\n  }\n\n  return result;\n}\n\n/**\n * Converts any JSON structure into row objects.\n */\nfunction flattenToRows(json: unknown): Record<string, string>[] {\n  if (Array.isArray(json)) {\n    return json.map((item) => flattenRecursive(item));\n  }\n\n  if (typeof json === 'object' && json !== null) {\n    return [flattenRecursive(json)];\n  }\n\n  throw new Error(\n    'JSON input must be an object or array of objects, not a bare primitive.'\n  );\n}\n\n/**\n * Escapes and quotes CSV cells according to options\n */\nfunction quoteCell(value: string, options: InitialValuesType): string {\n  const { delimiter, quoteStrings } = options;\n\n  const escaped = value.replace(/\"/g, '\"\"');\n\n  const needsQuoting =\n    value.includes(delimiter) ||\n    value.includes('\"') ||\n    value.includes('\\n') ||\n    value.includes('\\r');\n\n  if (quoteStrings === 'always') return `\"${escaped}\"`;\n\n  return needsQuoting ? `\"${escaped}\"` : value;\n}\n\n/**\n * Converts JSON string to CSV\n */\nexport function convertJsonToCsv(\n  input: string,\n  options: InitialValuesType\n): string {\n  const { delimiter, includeHeaders } = options;\n\n  if (!delimiter) throw new Error('No CSV delimiter.');\n\n  let parsed: unknown;\n\n  try {\n    parsed = JSON.parse(input);\n  } catch {\n    throw new Error('Invalid JSON input.');\n  }\n\n  const rows = flattenToRows(parsed).filter(\n    (row) => Object.keys(row).length > 0\n  );\n\n  if (rows.length === 0) {\n    throw new Error('No data found in the provided JSON.');\n  }\n\n  const headers = getJsonHeaders(rows);\n\n  const lines: string[] = [];\n\n  if (includeHeaders) {\n    lines.push(headers.map((h) => quoteCell(h, options)).join(delimiter));\n  }\n\n  for (const row of rows) {\n    const line = headers\n      .map((header) => quoteCell(row[header] ?? '', options))\n      .join(delimiter);\n\n    lines.push(line);\n  }\n\n  return lines.join('\\r\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/json/json-to-csv/types.ts",
    "content": "export type InitialValuesType = {\n  delimiter: string;\n  includeHeaders: boolean;\n  quoteStrings: 'always' | 'auto';\n};\n"
  },
  {
    "path": "src/pages/tools/json/json-to-xml/index.tsx",
    "content": "import { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolCodeInput from '@components/input/ToolCodeInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { convertJsonToXml } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { Box } from '@mui/material';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport SimpleRadio from '@components/options/SimpleRadio';\n\ntype InitialValuesType = {\n  indentationType: 'space' | 'tab' | 'none';\n  addMetaTag: boolean;\n};\n\nconst initialValues: InitialValuesType = {\n  indentationType: 'space',\n  addMetaTag: false\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Basic JSON to XML',\n    description: 'Convert a simple JSON object into an XML format.',\n    sampleText: `\n{\n  \"users\": [\n    {\n      \"name\": \"John\",\n      \"age\": 30,\n      \"city\": \"New York\"\n    },\n    {\n      \"name\": \"Alice\",\n      \"age\": 25,\n      \"city\": \"London\"\n    }\n  ]\n}`,\n    sampleResult: `<root>\n\\t<users>\n\\t\\t<name>John</name>\n\\t\\t<age>30</age>\n\\t\\t<city>New York</city>\n\\t</users>\n\\t<users>\n\\t\\t<name>Alice</name>\n\\t\\t<age>25</age>\n\\t\\t<city>London</city>\n\\t</users>\n</root>`,\n    sampleOptions: {\n      ...initialValues\n    }\n  }\n];\n\nexport default function JsonToXml({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    if (input) {\n      try {\n        const xmlResult = convertJsonToXml(input, values);\n        setResult(xmlResult);\n      } catch (error) {\n        setResult(\n          `Error: ${\n            error instanceof Error ? error.message : 'Invalid Json format'\n          }`\n        );\n      }\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={initialValues}\n      compute={compute}\n      exampleCards={exampleCards}\n      inputComponent={\n        <ToolCodeInput\n          title=\"Input Json\"\n          value={input}\n          onChange={setInput}\n          language=\"json\"\n        />\n      }\n      resultComponent={\n        <ToolTextResult title=\"Output XML\" value={result} extension={'xml'} />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: 'Output XML Indentation',\n          component: (\n            <Box>\n              <SimpleRadio\n                checked={values.indentationType === 'space'}\n                title={'Use Spaces for indentation'}\n                description={\n                  'Use spaces to visualize the hierarchical structure of XML.'\n                }\n                onClick={() => updateField('indentationType', 'space')}\n              />\n              <SimpleRadio\n                checked={values.indentationType === 'tab'}\n                title={'Use Tabs for indentation'}\n                description={\n                  'Use tabs to visualize the hierarchical structure of XML.'\n                }\n                onClick={() => updateField('indentationType', 'tab')}\n              />\n              <SimpleRadio\n                checked={values.indentationType === 'none'}\n                title={'No indentation'}\n                description={'Output XML without any indentation.'}\n                onClick={() => updateField('indentationType', 'none')}\n              />\n            </Box>\n          )\n        },\n        {\n          title: 'XML Meta Information',\n          component: (\n            <Box>\n              <CheckboxWithDesc\n                checked={values.addMetaTag}\n                onChange={(value) => updateField('addMetaTag', value)}\n                title=\"Add an XML Meta Tag\"\n                description=\"Add a meta tag at the beginning of the XML output.\"\n              />\n            </Box>\n          )\n        }\n      ]}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/json/json-to-xml/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('json', {\n  path: 'json-to-xml',\n  icon: 'material-symbols:code',\n\n  keywords: ['json', 'xml', 'convert', 'transform'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'json:jsonToXml.title',\n    description: 'json:jsonToXml.description',\n    shortDescription: 'json:jsonToXml.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/json/json-to-xml/service.ts",
    "content": "type JsonToXmlOptions = {\n  indentationType: 'space' | 'tab' | 'none';\n  addMetaTag: boolean;\n};\n\nexport const convertJsonToXml = (\n  json: string,\n  options: JsonToXmlOptions\n): string => {\n  const obj = JSON.parse(json);\n  return convertObjectToXml(obj, options);\n};\n\nconst getIndentation = (options: JsonToXmlOptions, depth: number): string => {\n  switch (options.indentationType) {\n    case 'space':\n      return '  '.repeat(depth + 1);\n    case 'tab':\n      return '\\t'.repeat(depth + 1);\n    case 'none':\n    default:\n      return '';\n  }\n};\n\nconst convertObjectToXml = (\n  obj: any,\n  options: JsonToXmlOptions,\n  depth: number = 0\n): string => {\n  let xml = '';\n\n  const newline = options.indentationType === 'none' ? '' : '\\n';\n\n  if (depth === 0) {\n    if (options.addMetaTag) {\n      xml += '<?xml version=\"1.0\" encoding=\"UTF-8\"?>' + newline;\n    }\n    xml += '<root>' + newline;\n  }\n\n  for (const key in obj) {\n    const value = obj[key];\n    const keyString = isNaN(Number(key)) ? key : `row-${key}`;\n\n    // Handle null values\n    if (value === null) {\n      xml += `${getIndentation(\n        options,\n        depth\n      )}<${keyString}></${keyString}>${newline}`;\n      continue;\n    }\n\n    // Handle arrays\n    if (Array.isArray(value)) {\n      value.forEach((item) => {\n        xml += `${getIndentation(options, depth)}<${keyString}>`;\n        if (item === null) {\n          xml += `</${keyString}>${newline}`;\n        } else if (typeof item === 'object') {\n          xml += `${newline}${convertObjectToXml(\n            item,\n            options,\n            depth + 1\n          )}${getIndentation(options, depth)}`;\n          xml += `</${keyString}>${newline}`;\n        } else {\n          xml += `${escapeXml(String(item))}</${keyString}>${newline}`;\n        }\n      });\n      continue;\n    }\n\n    // Handle objects\n    if (typeof value === 'object') {\n      xml += `${getIndentation(options, depth)}<${keyString}>${newline}`;\n      xml += convertObjectToXml(value, options, depth + 1);\n      xml += `${getIndentation(options, depth)}</${keyString}>${newline}`;\n      continue;\n    }\n\n    // Handle primitive values (string, number, boolean, etc.)\n    xml += `${getIndentation(options, depth)}<${keyString}>${escapeXml(\n      String(value)\n    )}</${keyString}>${newline}`;\n  }\n\n  return depth === 0 ? `${xml}</root>` : xml;\n};\n\nconst escapeXml = (str: string): string => {\n  return str\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&apos;');\n};\n"
  },
  {
    "path": "src/pages/tools/json/minify/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolCodeInput from '@components/input/ToolCodeInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { minifyJson } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\n\ntype InitialValuesType = Record<string, never>;\n\nconst initialValues: InitialValuesType = {};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Minify a Simple JSON Object',\n    description:\n      'This example shows how to minify a simple JSON object by removing all unnecessary whitespace.',\n    sampleText: `{\n  \"name\": \"John Doe\",\n  \"age\": 30,\n  \"city\": \"New York\"\n}`,\n    sampleResult: `{\"name\":\"John Doe\",\"age\":30,\"city\":\"New York\"}`,\n    sampleOptions: {}\n  },\n  {\n    title: 'Minify a Nested JSON Structure',\n    description:\n      'This example demonstrates minification of a complex nested JSON structure with arrays and objects.',\n    sampleText: `{\n  \"users\": [\n    {\n      \"id\": 1,\n      \"name\": \"Alice\",\n      \"hobbies\": [\"reading\", \"gaming\"]\n    },\n    {\n      \"id\": 2,\n      \"name\": \"Bob\",\n      \"hobbies\": [\"swimming\", \"coding\"]\n    }\n  ]\n}`,\n    sampleResult: `{\"users\":[{\"id\":1,\"name\":\"Alice\",\"hobbies\":[\"reading\",\"gaming\"]},{\"id\":2,\"name\":\"Bob\",\"hobbies\":[\"swimming\",\"coding\"]}]}`,\n    sampleOptions: {}\n  }\n];\n\nexport default function MinifyJson({ title }: ToolComponentProps) {\n  const { t } = useTranslation('json');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (_: InitialValuesType, input: string) => {\n    if (input) setResult(minifyJson(input));\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolCodeInput\n          title={t('minify.inputTitle')}\n          value={input}\n          onChange={setInput}\n          language=\"json\"\n        />\n      }\n      resultComponent={\n        <ToolTextResult\n          title={t('minify.resultTitle')}\n          value={result}\n          extension={'json'}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={null}\n      toolInfo={{\n        title: t('minify.toolInfo.title'),\n        description: t('minify.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n      input={input}\n      setInput={setInput}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/json/minify/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('json', {\n  path: 'minify',\n  icon: 'material-symbols:code',\n\n  keywords: ['json', 'minify', 'compress', 'whitespace'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'json:minify.title',\n    description: 'json:minify.description',\n    shortDescription: 'json:minify.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/json/minify/service.ts",
    "content": "export const minifyJson = (text: string) => {\n  let parsedJson;\n  try {\n    parsedJson = JSON.parse(text);\n  } catch (e) {\n    throw new Error('Invalid JSON string');\n  }\n\n  return JSON.stringify(parsedJson);\n};\n"
  },
  {
    "path": "src/pages/tools/json/prettify/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useRef, useState } from 'react';\nimport ToolCodeInput from '@components/input/ToolCodeInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { beautifyJson } from './service';\nimport ToolInfo from '@components/ToolInfo';\nimport Separator from '@components/Separator';\nimport ToolExamples, {\n  CardExampleType\n} from '@components/examples/ToolExamples';\nimport { FormikProps } from 'formik';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport RadioWithTextField from '@components/options/RadioWithTextField';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport { isNumber, updateNumberField } from '../../../../utils/string';\nimport ToolContent from '@components/ToolContent';\nimport { useTranslation } from 'react-i18next';\n\ntype InitialValuesType = {\n  indentationType: 'tab' | 'space';\n  spacesCount: number;\n};\n\nconst initialValues: InitialValuesType = {\n  indentationType: 'space',\n  spacesCount: 2\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Beautify an Ugly JSON Array',\n    description:\n      'In this example, we prettify an ugly JSON array. The input data is a one-dimensional array of numbers [1,2,3] but they are all over the place. This array gets cleaned up and transformed into a more readable format where each element is on a new line with an appropriate indentation using four spaces.',\n    sampleText: `[\n 1,\n2,3\n]`,\n    sampleResult: `[\n    1,\n    2,\n    3\n]`,\n    sampleOptions: {\n      indentationType: 'space',\n      spacesCount: 4\n    }\n  },\n  {\n    title: 'Prettify a Complex JSON Object',\n    description:\n      'In this example, we prettify a complex JSON data structure consisting of arrays and objects. The input data is a minified JSON object with multiple data structure depth levels. To make it neat and readable, we add two spaces for indentation to each depth level, making the JSON structure clear and easy to understand.',\n    sampleText: `{\"names\":[\"jack\",\"john\",\"alex\"],\"hobbies\":{\"jack\":[\"programming\",\"rock climbing\"],\"john\":[\"running\",\"racing\"],\"alex\":[\"dancing\",\"fencing\"]}}`,\n    sampleResult: `{\n  \"names\": [\n    \"jack\",\n    \"john\",\n    \"alex\"\n  ],\n  \"hobbies\": {\n    \"jack\": [\n      \"programming\",\n      \"rock climbing\"\n    ],\n    \"john\": [\n      \"running\",\n      \"racing\"\n    ],\n    \"alex\": [\n      \"dancing\",\n      \"fencing\"\n    ]\n  }\n}`,\n    sampleOptions: {\n      indentationType: 'space',\n      spacesCount: 2\n    }\n  },\n  {\n    title: 'Beautify a JSON with Excessive Whitespace',\n    description:\n      \"In this example, we show how the JSON prettify tool can handle code with excessive whitespace. The input file has many leading and trailing spaces as well as spaces within the objects. The excessive whitespace makes the file bulky and hard to read and leads to a bad impression of the programmer who wrote it. The program removes all these unnecessary spaces and creates a proper data hierarchy that's easy to work with by adding indentation via tabs.\",\n    sampleText: `\n{\n     \"name\":  \"The Name of the Wind\",\n \"author\"  : \"Patrick Rothfuss\",\n     \"genre\"  :  \"Fantasy\",\n     \"published\"   : 2007,\n   \"rating\"    :  {\n \"average\"   :   4.6,\n \"goodreads\"         :   4.58,\n      \"amazon\"   :  4.4\n },\n      \"is_fiction\" : true\n    }\n\n\n`,\n    sampleResult: `{\n\\t\"name\": \"The Name of the Wind\",\n\\t\"author\": \"Patrick Rothfuss\",\n\\t\"genre\": \"Fantasy\",\n\\t\"published\": 2007,\n\\t\"rating\": {\n\\t\\t\"average\": 4.6,\n\\t\\t\"goodreads\": 4.58,\n\\t\\t\"amazon\": 4.4\n\\t},\n\\t\"is_fiction\": true\n}`,\n    sampleOptions: {\n      indentationType: 'tab',\n      spacesCount: 0\n    }\n  }\n];\n\nexport default function PrettifyJson({ title }: ToolComponentProps) {\n  const { t } = useTranslation('json');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: any) => {\n    const { indentationType, spacesCount } = optionsValues;\n    if (input) setResult(beautifyJson(input, indentationType, spacesCount));\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolCodeInput\n          title={t('prettify.inputTitle')}\n          value={input}\n          onChange={setInput}\n          language=\"json\"\n        />\n      }\n      resultComponent={\n        <ToolTextResult\n          title={t('prettify.resultTitle')}\n          value={result}\n          extension={'json'}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('prettify.indentation'),\n          component: (\n            <Box>\n              <RadioWithTextField\n                checked={values.indentationType === 'space'}\n                title={t('prettify.useSpaces')}\n                fieldName={'indentationType'}\n                description={t('prettify.useSpacesDescription')}\n                value={values.spacesCount.toString()}\n                onRadioClick={() => updateField('indentationType', 'space')}\n                onTextChange={(val) =>\n                  updateNumberField(val, 'spacesCount', updateField)\n                }\n              />\n              <SimpleRadio\n                onClick={() => updateField('indentationType', 'tab')}\n                checked={values.indentationType === 'tab'}\n                description={t('prettify.useTabsDescription')}\n                title={t('prettify.useTabs')}\n              />\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n      setInput={setInput}\n      exampleCards={exampleCards}\n      toolInfo={{\n        title: t('prettify.toolInfo.title'),\n        description: t('prettify.toolInfo.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/json/prettify/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('json', {\n  path: 'prettify',\n  icon: 'material-symbols:code',\n\n  keywords: ['json', 'prettify', 'format', 'beautify'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'json:prettify.title',\n    description: 'json:prettify.description',\n    shortDescription: 'json:prettify.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/json/prettify/service.ts",
    "content": "export const beautifyJson = (\n  text: string,\n  indentationType: 'tab' | 'space',\n  spacesCount: number\n) => {\n  let parsedJson;\n  try {\n    parsedJson = JSON.parse(text);\n  } catch (e) {\n    throw new Error('Invalid JSON string');\n  }\n\n  const indent = indentationType === 'tab' ? '\\t' : spacesCount;\n\n  return JSON.stringify(parsedJson, null, indent);\n};\n"
  },
  {
    "path": "src/pages/tools/json/stringify/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolCodeInput from '@components/input/ToolCodeInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { stringifyJson } from './service';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport RadioWithTextField from '@components/options/RadioWithTextField';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { isNumber, updateNumberField } from '@utils/string';\nimport { CardExampleType } from '@components/examples/ToolExamples';\n\ntype InitialValuesType = {\n  indentationType: 'tab' | 'space';\n  spacesCount: number;\n  escapeHtml: boolean;\n};\n\nconst initialValues: InitialValuesType = {\n  indentationType: 'space',\n  spacesCount: 2,\n  escapeHtml: false\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Simple Object to JSON',\n    description: 'Convert a basic JavaScript object into a JSON string.',\n    sampleText: `{ name: \"John\", age: 30 }`,\n    sampleResult: `{\n  \"name\": \"John\",\n  \"age\": 30\n}`,\n    sampleOptions: {\n      indentationType: 'space',\n      spacesCount: 2,\n      escapeHtml: false\n    }\n  },\n  {\n    title: 'Array with Mixed Types',\n    description:\n      'Convert an array containing different types of values into JSON.',\n    sampleText: `[1, \"hello\", true, null, { x: 10 }]`,\n    sampleResult: `[\n    1,\n    \"hello\",\n    true,\n    null,\n    {\n        \"x\": 10\n    }\n]`,\n    sampleOptions: {\n      indentationType: 'space',\n      spacesCount: 4,\n      escapeHtml: false\n    }\n  },\n  {\n    title: 'HTML-Escaped JSON',\n    description: 'Convert an object to JSON with HTML characters escaped.',\n    sampleText: `{\n  html: \"<div>Hello & Welcome</div>\",\n  message: \"Special chars: < > & ' \\\\\"\"\n}`,\n    sampleResult: `{\n  &quot;html&quot;: &quot;&lt;div&gt;Hello &amp; Welcome&lt;/div&gt;&quot;,\n  &quot;message&quot;: &quot;Special chars: &lt; &gt; &amp; &#039; &quot;&quot;\n}`,\n    sampleOptions: {\n      indentationType: 'space',\n      spacesCount: 2,\n      escapeHtml: true\n    }\n  }\n];\n\nexport default function StringifyJson({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    if (input) {\n      setResult(\n        stringifyJson(\n          input,\n          values.indentationType,\n          values.spacesCount,\n          values.escapeHtml\n        )\n      );\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={initialValues}\n      compute={compute}\n      exampleCards={exampleCards}\n      inputComponent={\n        <ToolCodeInput\n          title=\"JavaScript Object/Array\"\n          value={input}\n          onChange={setInput}\n          language=\"json\"\n        />\n      }\n      resultComponent={\n        <ToolTextResult title=\"JSON String\" value={result} extension={'json'} />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: 'Indentation',\n          component: (\n            <Box>\n              <RadioWithTextField\n                checked={values.indentationType === 'space'}\n                title=\"Use Spaces\"\n                fieldName=\"indentationType\"\n                description=\"Indent output with spaces\"\n                value={values.spacesCount.toString()}\n                onRadioClick={() => updateField('indentationType', 'space')}\n                onTextChange={(val) =>\n                  updateNumberField(val, 'spacesCount', updateField)\n                }\n              />\n              <SimpleRadio\n                onClick={() => updateField('indentationType', 'tab')}\n                checked={values.indentationType === 'tab'}\n                description=\"Indent output with tabs\"\n                title=\"Use Tabs\"\n              />\n            </Box>\n          )\n        },\n        {\n          title: 'Options',\n          component: (\n            <CheckboxWithDesc\n              checked={values.escapeHtml}\n              onChange={(value) => updateField('escapeHtml', value)}\n              title=\"Escape HTML Characters\"\n              description=\"Convert HTML special characters to their entity references\"\n            />\n          )\n        }\n      ]}\n      toolInfo={{\n        title: 'What Is JSON Stringify?',\n        description:\n          'JSON Stringify is a tool that converts JavaScript objects and arrays into their JSON string representation. It properly formats the output with customizable indentation and offers the option to escape HTML special characters, making it safe for web usage. This tool is particularly useful when you need to serialize data structures for storage or transmission, or when you need to prepare JSON data for HTML embedding.'\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/json/stringify/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('json', {\n  path: 'stringify',\n  icon: 'material-symbols:code',\n\n  keywords: ['json', 'stringify', 'serialize', 'convert'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'json:stringify.title',\n    description: 'json:stringify.description',\n    shortDescription: 'json:stringify.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/json/stringify/service.ts",
    "content": "export const stringifyJson = (\n  input: string,\n  indentationType: 'tab' | 'space',\n  spacesCount: number,\n  escapeHtml: boolean\n): string => {\n  let parsedInput;\n  try {\n    // Safely evaluate the input string as JavaScript\n    parsedInput = eval('(' + input + ')');\n  } catch (e) {\n    throw new Error('Invalid JavaScript object/array');\n  }\n\n  const indent = indentationType === 'tab' ? '\\t' : ' '.repeat(spacesCount);\n  let result = JSON.stringify(parsedInput, null, indent);\n\n  if (escapeHtml) {\n    result = result\n      .replace(/&/g, '&amp;')\n      .replace(/</g, '&lt;')\n      .replace(/>/g, '&gt;')\n      .replace(/\"/g, '&quot;')\n      .replace(/'/g, '&#039;');\n  }\n\n  return result;\n};\n"
  },
  {
    "path": "src/pages/tools/json/validateJson/index.tsx",
    "content": "import React, { useState } from 'react';\r\nimport ToolCodeInput from '@components/input/ToolCodeInput';\r\nimport ToolTextResult from '@components/result/ToolTextResult';\r\nimport { CardExampleType } from '@components/examples/ToolExamples';\r\nimport { validateJson } from './service';\r\nimport { ToolComponentProps } from '@tools/defineTool';\r\nimport ToolContent from '@components/ToolContent';\r\nimport { useTranslation } from 'react-i18next';\r\n\r\nconst exampleCards: CardExampleType<{}>[] = [\r\n  {\r\n    title: 'Valid JSON Object',\r\n    description:\r\n      'This example shows a correctly formatted JSON object. All property names and string values are enclosed in double quotes, and the overall structure is properly balanced with opening and closing braces.',\r\n    sampleText: `{\r\n  \"name\": \"John\",\r\n  \"age\": 30,\r\n  \"city\": \"New York\"\r\n}`,\r\n    sampleResult: '✅ Valid JSON',\r\n    sampleOptions: {}\r\n  },\r\n  {\r\n    title: 'Invalid JSON Missing Quotes',\r\n    description:\r\n      'This example demonstrates an invalid JSON object where the property names are not enclosed in double quotes. According to the JSON standard, property names must always be enclosed in double quotes. Omitting the quotes will result in a syntax error.',\r\n    sampleText: `{\r\n  name: \"John\",\r\n  age: 30,\r\n  city: \"New York\"\r\n}`,\r\n    sampleResult: \"❌ Error: Expected property name or '}' in JSON\",\r\n    sampleOptions: {}\r\n  },\r\n  {\r\n    title: 'Invalid JSON with Trailing Comma',\r\n    description:\r\n      'This example shows an invalid JSON object with a trailing comma after the last key-value pair. In JSON, trailing commas are not allowed because they create ambiguity when parsing the data structure.',\r\n    sampleText: `{\r\n  \"name\": \"John\",\r\n  \"age\": 30,\r\n  \"city\": \"New York\",\r\n}`,\r\n    sampleResult: '❌ Error: Expected double-quoted property name',\r\n    sampleOptions: {}\r\n  }\r\n];\r\n\r\nexport default function ValidateJson({ title }: ToolComponentProps) {\r\n  const { t } = useTranslation('json');\r\n  const [input, setInput] = useState<string>('');\r\n  const [result, setResult] = useState<string>('');\r\n\r\n  const compute = (options: any, input: string) => {\r\n    const { valid, error } = validateJson(input);\r\n\r\n    if (valid) {\r\n      setResult(t('validateJson.validJson'));\r\n    } else {\r\n      setResult(t('validateJson.invalidJson', { error }));\r\n    }\r\n  };\r\n\r\n  return (\r\n    <ToolContent\r\n      title={title}\r\n      inputComponent={\r\n        <ToolCodeInput\r\n          title={t('validateJson.inputTitle')}\r\n          value={input}\r\n          onChange={setInput}\r\n          language=\"json\"\r\n        />\r\n      }\r\n      resultComponent={\r\n        <ToolTextResult title={t('validateJson.resultTitle')} value={result} />\r\n      }\r\n      initialValues={{}}\r\n      getGroups={null}\r\n      toolInfo={{\r\n        title: t('validateJson.toolInfo.title'),\r\n        description: t('validateJson.toolInfo.description')\r\n      }}\r\n      exampleCards={exampleCards}\r\n      input={input}\r\n      setInput={setInput}\r\n      compute={compute}\r\n    />\r\n  );\r\n}\r\n"
  },
  {
    "path": "src/pages/tools/json/validateJson/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('json', {\n  path: 'validateJson',\n  icon: 'material-symbols:check-circle',\n\n  keywords: ['json', 'validate', 'check', 'syntax', 'errors'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'json:validateJson.title',\n    description: 'json:validateJson.description',\n    shortDescription: 'json:validateJson.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/json/validateJson/service.ts",
    "content": "export const validateJson = (\n  input: string\n): { valid: boolean; error?: string } => {\n  try {\n    JSON.parse(input);\n    return { valid: true };\n  } catch (error) {\n    if (error instanceof SyntaxError) {\n      return { valid: false, error: error.message };\n    }\n    return { valid: false, error: 'Unknown error occurred' };\n  }\n};\n"
  },
  {
    "path": "src/pages/tools/list/duplicate/duplicate.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { duplicateList } from './service';\n\ndescribe('duplicateList function', () => {\n  it('should duplicate elements correctly with symbol split', () => {\n    const input = 'Hello World';\n    const result = duplicateList('symbol', ' ', ' ', input, true, false, 2);\n    expect(result).toBe('Hello World Hello World');\n  });\n\n  it('should duplicate elements correctly with regex split', () => {\n    const input = 'Hello||World';\n    const result = duplicateList('regex', '\\\\|\\\\|', ' ', input, true, false, 2);\n    expect(result).toBe('Hello World Hello World');\n  });\n\n  it('should handle fractional duplication', () => {\n    const input = 'Hello World';\n    const result = duplicateList('symbol', ' ', ' ', input, true, false, 1.5);\n    expect(result).toBe('Hello World Hello');\n  });\n\n  it('should handle reverse option correctly', () => {\n    const input = 'Hello World';\n    const result = duplicateList('symbol', ' ', ' ', input, true, true, 2);\n    expect(result).toBe('Hello World World Hello');\n  });\n\n  it('should handle concatenate option correctly', () => {\n    const input = 'Hello World';\n    const result = duplicateList('symbol', ' ', ' ', input, false, false, 2);\n    expect(result).toBe('Hello Hello World World');\n  });\n\n  it('should handle interweaving option correctly', () => {\n    const input = 'Hello World';\n    const result = duplicateList('symbol', ' ', ' ', input, false, false, 2);\n    expect(result).toBe('Hello Hello World World');\n  });\n\n  it('should throw an error for negative copies', () => {\n    expect(() =>\n      duplicateList('symbol', ' ', ' ', 'Hello World', true, false, -1)\n    ).toThrow('Number of copies cannot be negative');\n  });\n\n  it('should handle interweaving option correctly 2', () => {\n    const input = \"je m'appelle king\";\n    const result = duplicateList('symbol', ' ', ', ', input, false, true, 2.1);\n    expect(result).toBe(\"je, king, m'appelle, m'appelle, king, je\");\n  });\n\n  it('should handle interweaving option correctly 3', () => {\n    const input = \"je m'appelle king\";\n    const result = duplicateList('symbol', ' ', ', ', input, false, true, 1);\n    expect(result).toBe(\"je, m'appelle, king\");\n  });\n\n  it('should handle interweaving option correctly 3', () => {\n    const input = \"je m'appelle king\";\n    const result = duplicateList('symbol', ' ', ', ', input, true, true, 2.7);\n    expect(result).toBe(\n      \"je, m'appelle, king, king, m'appelle, je, king, m'appelle\"\n    );\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/list/duplicate/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Box } from '@mui/material';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { duplicateList, SplitOperatorType } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport * as Yup from 'yup';\nimport { useTranslation } from 'react-i18next';\n\ninterface InitialValuesType {\n  splitOperatorType: SplitOperatorType;\n  splitSeparator: string;\n  joinSeparator: string;\n  concatenate: boolean;\n  reverse: boolean;\n  copy: string;\n}\n\nconst initialValues: InitialValuesType = {\n  splitOperatorType: 'symbol',\n  splitSeparator: ' ',\n  joinSeparator: ' ',\n  concatenate: true,\n  reverse: false,\n  copy: '2'\n};\n\nconst validationSchema = Yup.object({\n  splitSeparator: Yup.string().required('The separator is required'),\n  joinSeparator: Yup.string().required('The join separator is required'),\n  copy: Yup.number()\n    .typeError('Number of copies must be a number')\n    .min(0.1, 'Number of copies must be positive')\n    .required('Number of copies is required')\n});\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Simple duplication',\n    description: 'This example shows how to duplicate a list of words.',\n    sampleText: 'Hello World',\n    sampleResult: 'Hello World Hello World',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ' ',\n      joinSeparator: ' ',\n      concatenate: true,\n      reverse: false,\n      copy: '2'\n    }\n  },\n  {\n    title: 'Reverse duplication',\n    description: 'This example shows how to duplicate a list in reverse order.',\n    sampleText: 'Hello World',\n    sampleResult: 'Hello World World Hello',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ' ',\n      joinSeparator: ' ',\n      concatenate: true,\n      reverse: true,\n      copy: '2'\n    }\n  },\n  {\n    title: 'Interweaving items',\n    description:\n      'This example shows how to interweave items instead of concatenating them.',\n    sampleText: 'Hello World',\n    sampleResult: 'Hello Hello World World',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ' ',\n      joinSeparator: ' ',\n      concatenate: false,\n      reverse: false,\n      copy: '2'\n    }\n  },\n  {\n    title: 'Fractional duplication',\n    description:\n      'This example shows how to duplicate a list with a fractional number of copies.',\n    sampleText: 'apple banana cherry',\n    sampleResult: 'apple banana cherry apple banana',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ' ',\n      joinSeparator: ' ',\n      concatenate: true,\n      reverse: false,\n      copy: '1.7'\n    }\n  }\n];\n\nexport default function Duplicate({ title }: ToolComponentProps) {\n  const { t } = useTranslation('list');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: string) => {\n    if (input) {\n      try {\n        const copy = parseFloat(optionsValues.copy);\n        setResult(\n          duplicateList(\n            optionsValues.splitOperatorType,\n            optionsValues.splitSeparator,\n            optionsValues.joinSeparator,\n            input,\n            optionsValues.concatenate,\n            optionsValues.reverse,\n            copy\n          )\n        );\n      } catch (error) {\n        if (error instanceof Error) {\n          setResult(`${t('duplicate.error')}: ${error.message}`);\n        } else {\n          setResult(t('duplicate.unknownError'));\n        }\n      }\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('duplicate.splitOptions'),\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('splitOperatorType', 'symbol')}\n            checked={values.splitOperatorType === 'symbol'}\n            title={t('duplicate.splitBySymbol')}\n          />\n          <SimpleRadio\n            onClick={() => updateField('splitOperatorType', 'regex')}\n            checked={values.splitOperatorType === 'regex'}\n            title={t('duplicate.splitByRegex')}\n          />\n          <TextFieldWithDesc\n            value={values.splitSeparator}\n            onOwnChange={(val) => updateField('splitSeparator', val)}\n            description={t('duplicate.splitSeparatorDescription')}\n          />\n          <TextFieldWithDesc\n            value={values.joinSeparator}\n            onOwnChange={(val) => updateField('joinSeparator', val)}\n            description={t('duplicate.joinSeparatorDescription')}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('duplicate.duplicationOptions'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.copy}\n            onOwnChange={(val) => updateField('copy', val)}\n            description={t('duplicate.copyDescription')}\n            type=\"number\"\n          />\n          <CheckboxWithDesc\n            title={t('duplicate.concatenate')}\n            checked={values.concatenate}\n            onChange={(checked) => updateField('concatenate', checked)}\n            description={t('duplicate.concatenateDescription')}\n          />\n          <CheckboxWithDesc\n            title={t('duplicate.reverse')}\n            checked={values.reverse}\n            onChange={(checked) => updateField('reverse', checked)}\n            description={t('duplicate.reverseDescription')}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolTextInput\n          title={t('duplicate.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('duplicate.resultTitle')} value={result} />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      validationSchema={validationSchema}\n      toolInfo={{\n        title: t('duplicate.toolInfo.title'),\n        description: t('duplicate.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n      input={input}\n      setInput={setInput}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/list/duplicate/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('list', {\n  path: 'duplicate',\n  icon: 'material-symbols-light:content-copy',\n\n  keywords: ['duplicate'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'list:duplicate.title',\n    description: 'list:duplicate.description',\n    shortDescription: 'list:duplicate.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/list/duplicate/service.ts",
    "content": "export type SplitOperatorType = 'symbol' | 'regex';\n\nfunction interweave(array1: string[], array2: string[]) {\n  const result: string[] = [];\n  const maxLength = Math.max(array1.length, array2.length);\n\n  for (let i = 0; i < maxLength; i++) {\n    if (i < array1.length) result.push(array1[i]);\n    if (i < array2.length) result.push(array2[i]);\n  }\n  return result;\n}\nfunction duplicate(\n  input: string[],\n  concatenate: boolean,\n  reverse: boolean,\n  copy?: number\n) {\n  if (copy) {\n    if (copy > 0) {\n      let result: string[] = [];\n      let toAdd: string[] = [];\n      let WholePart: string[] = [];\n      let fractionalPart: string[] = [];\n      const whole = Math.floor(copy);\n      const fractional = copy - whole;\n      if (!reverse) {\n        WholePart = concatenate\n          ? Array(whole).fill(input).flat()\n          : Array(whole - 1)\n              .fill(input)\n              .flat();\n        fractionalPart = input.slice(0, Math.floor(input.length * fractional));\n        toAdd = WholePart.concat(fractionalPart);\n        result = concatenate\n          ? WholePart.concat(fractionalPart)\n          : interweave(input, toAdd);\n      } else {\n        WholePart = Array(whole - 1)\n          .fill(input)\n          .flat()\n          .reverse();\n        fractionalPart = input\n          .slice()\n          .reverse()\n          .slice(0, Math.floor(input.length * fractional));\n        toAdd = WholePart.concat(fractionalPart);\n        result = concatenate ? input.concat(toAdd) : interweave(input, toAdd);\n      }\n\n      return result;\n    }\n    throw new Error('Number of copies cannot be negative');\n  }\n  throw new Error('Number of copies must be a valid number');\n}\n\nexport function duplicateList(\n  splitOperatorType: SplitOperatorType,\n  splitSeparator: string,\n  joinSeparator: string,\n  input: string,\n  concatenate: boolean,\n  reverse: boolean,\n  copy?: number\n): string {\n  let array: string[];\n  let result: string[];\n  switch (splitOperatorType) {\n    case 'symbol':\n      array = input.split(splitSeparator);\n      break;\n    case 'regex':\n      array = input\n        .split(new RegExp(splitSeparator))\n        .filter((item) => item !== '');\n      break;\n  }\n  result = duplicate(array, concatenate, reverse, copy);\n  return result.join(joinSeparator);\n}\n"
  },
  {
    "path": "src/pages/tools/list/find-most-popular/find-most-popular.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { TopItemsList } from './service';\n\ndescribe('TopItemsList function', () => {\n  it('should handle sorting alphabetically ignoring case', () => {\n    const input = 'Apple,banana,apple,Orange,Banana,apple';\n    const result = TopItemsList(\n      'symbol',\n      'alphabetic',\n      'count',\n      ',',\n      input,\n      false,\n      true,\n      false\n    );\n    expect(result).toEqual('apple: 3\\n' + 'banana: 2\\n' + 'orange: 1');\n  });\n\n  it('should handle sorting by count and not ignoring case', () => {\n    const input = 'apple,banana,apple,orange,banana,apple,Banana';\n    const result = TopItemsList(\n      'symbol',\n      'count',\n      'count',\n      ',',\n      input,\n      false,\n      false,\n      false\n    );\n    expect(result).toEqual(\n      'apple: 3\\n' + 'banana: 2\\n' + 'orange: 1\\n' + 'Banana: 1'\n    );\n  });\n\n  it('should handle regex split operator', () => {\n    const input = 'apple123banana456apple789orange012banana345apple678';\n    const result = TopItemsList(\n      'regex',\n      'count',\n      'count',\n      '\\\\d+',\n      input,\n      false,\n      false,\n      false\n    );\n    expect(result).toEqual('apple: 3\\n' + 'banana: 2\\n' + 'orange: 1');\n  });\n\n  it('should handle percentage display format', () => {\n    const input = 'apple,banana,apple,orange,banana,apple';\n    const result = TopItemsList(\n      'symbol',\n      'count',\n      'percentage',\n      ',',\n      input,\n      false,\n      false,\n      false\n    );\n    expect(result).toEqual(\n      'apple: 3 (50.00%)\\n' + 'banana: 2 (33.33%)\\n' + 'orange: 1 (16.67%)'\n    );\n  });\n\n  it('should handle total display format', () => {\n    const input = 'apple,banana,apple,orange,banana,apple';\n    const result = TopItemsList(\n      'symbol',\n      'count',\n      'total',\n      ',',\n      input,\n      false,\n      false,\n      false\n    );\n    expect(result).toEqual(\n      'apple: 3 (3 / 6)\\n' + 'banana: 2 (2 / 6)\\n' + 'orange: 1 (1 / 6)'\n    );\n  });\n\n  it('should handle trimming and ignoring empty items', () => {\n    const input = ' apple , banana , apple , orange , banana , apple ';\n    const result = TopItemsList(\n      'symbol',\n      'count',\n      'count',\n      ',',\n      input,\n      true,\n      false,\n      true\n    );\n    expect(result).toEqual('apple: 3\\n' + 'banana: 2\\n' + 'orange: 1');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/list/find-most-popular/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport {\n  DisplayFormat,\n  SortingMethod,\n  SplitOperatorType,\n  TopItemsList\n} from './service';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { ParseKeys } from 'i18next';\n\nconst initialValues = {\n  splitSeparatorType: 'symbol' as SplitOperatorType,\n  sortingMethod: 'alphabetic' as SortingMethod,\n  displayFormat: 'count' as DisplayFormat,\n  splitSeparator: ',',\n  deleteEmptyItems: false,\n  ignoreItemCase: false,\n  trimItems: false\n};\nconst splitOperators: {\n  title: ParseKeys<'list'>;\n  description: ParseKeys<'list'>;\n  type: SplitOperatorType;\n}[] = [\n  {\n    title: 'findMostPopular.splitOperators.symbol.title',\n    description: 'findMostPopular.splitOperators.symbol.description',\n    type: 'symbol'\n  },\n  {\n    title: 'findMostPopular.splitOperators.regex.title',\n    type: 'regex',\n    description: 'findMostPopular.splitOperators.regex.description'\n  }\n];\n\nexport default function FindMostPopular({ title }: ToolComponentProps) {\n  const { t } = useTranslation('list');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n  const compute = (optionsValues: typeof initialValues, input: any) => {\n    const {\n      splitSeparatorType,\n      splitSeparator,\n      displayFormat,\n      sortingMethod,\n      deleteEmptyItems,\n      ignoreItemCase,\n      trimItems\n    } = optionsValues;\n\n    setResult(\n      TopItemsList(\n        splitSeparatorType,\n        sortingMethod,\n        displayFormat,\n        splitSeparator,\n        input,\n        deleteEmptyItems,\n        ignoreItemCase,\n        trimItems\n      )\n    );\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput\n          title={t('findMostPopular.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult\n          title={t('findMostPopular.resultTitle')}\n          value={result}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('findMostPopular.extractListItems'),\n          component: (\n            <Box>\n              {splitOperators.map(({ title, description, type }) => (\n                <SimpleRadio\n                  key={type}\n                  onClick={() => updateField('splitSeparatorType', type)}\n                  title={t(title)}\n                  description={t(description)}\n                  checked={values.splitSeparatorType === type}\n                />\n              ))}\n              <TextFieldWithDesc\n                description={t('findMostPopular.splitSeparatorDescription')}\n                value={values.splitSeparator}\n                onOwnChange={(val) => updateField('splitSeparator', val)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('findMostPopular.itemComparison'),\n          component: (\n            <Box>\n              <CheckboxWithDesc\n                title={t('findMostPopular.removeEmptyItems')}\n                description={t('findMostPopular.removeEmptyItemsDescription')}\n                checked={values.deleteEmptyItems}\n                onChange={(value) => updateField('deleteEmptyItems', value)}\n              />\n              <CheckboxWithDesc\n                title={t('findMostPopular.trimItems')}\n                description={t('findMostPopular.trimItemsDescription')}\n                checked={values.trimItems}\n                onChange={(value) => updateField('trimItems', value)}\n              />\n              <CheckboxWithDesc\n                title={t('findMostPopular.ignoreItemCase')}\n                description={t('findMostPopular.ignoreItemCaseDescription')}\n                checked={values.ignoreItemCase}\n                onChange={(value) => updateField('ignoreItemCase', value)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('findMostPopular.outputFormat'),\n          component: (\n            <Box>\n              <SelectWithDesc\n                selected={values.displayFormat}\n                options={[\n                  {\n                    label: t('findMostPopular.displayOptions.percentage'),\n                    value: 'percentage'\n                  },\n                  {\n                    label: t('findMostPopular.displayOptions.count'),\n                    value: 'count'\n                  },\n                  {\n                    label: t('findMostPopular.displayOptions.total'),\n                    value: 'total'\n                  }\n                ]}\n                onChange={(value) => updateField('displayFormat', value)}\n                description={t('findMostPopular.displayFormatDescription')}\n              />\n              <SelectWithDesc\n                selected={values.sortingMethod}\n                options={[\n                  {\n                    label: t('findMostPopular.sortOptions.alphabetic'),\n                    value: 'alphabetic'\n                  },\n                  {\n                    label: t('findMostPopular.sortOptions.count'),\n                    value: 'count'\n                  }\n                ]}\n                onChange={(value) => updateField('sortingMethod', value)}\n                description={t('findMostPopular.sortingMethodDescription')}\n              />\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n      setInput={setInput}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/list/find-most-popular/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('list', {\n  path: 'find-most-popular',\n  icon: 'material-symbols-light:trending-up',\n\n  keywords: ['find', 'most', 'popular'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'list:findMostPopular.title',\n    description: 'list:findMostPopular.description',\n    shortDescription: 'list:findMostPopular.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/list/find-most-popular/service.ts",
    "content": "import { itemCounter } from '@utils/string';\n\nexport type SplitOperatorType = 'symbol' | 'regex';\nexport type DisplayFormat = 'count' | 'percentage' | 'total';\nexport type SortingMethod = 'count' | 'alphabetic';\n\n// Function that sorts the dict created with dictMaker based on the chosen sorting method\nfunction dictSorter(\n  dict: { [key: string]: number },\n  sortingMethod: SortingMethod\n): { [key: string]: number } {\n  let sortedArray: [string, number][];\n  switch (sortingMethod) {\n    case 'count':\n      sortedArray = Object.entries(dict).sort(\n        ([, countA], [, countB]) => countB - countA\n      );\n      break;\n    case 'alphabetic':\n      sortedArray = Object.entries(dict).sort(([keyA], [keyB]) => {\n        return keyA.localeCompare(keyB);\n      });\n      break;\n    default:\n      sortedArray = Object.entries(dict);\n      break;\n  }\n  return Object.fromEntries(sortedArray);\n}\n\n// Function that prepares the output of dictSorter based on the chosen display format\nfunction displayFormater(\n  dict: { [key: string]: number },\n  displayFormat: DisplayFormat\n): string[] {\n  const formattedOutput: string[] = [];\n  const total = Object.values(dict).reduce((acc, val) => acc + val, 0);\n\n  switch (displayFormat) {\n    case 'percentage':\n      Object.entries(dict).forEach(([key, value]) => {\n        formattedOutput.push(\n          `${key}: ${value} (${((value / total) * 100).toFixed(2)}%)`\n        );\n      });\n      break;\n    case 'total':\n      Object.entries(dict).forEach(([key, value]) => {\n        formattedOutput.push(`${key}: ${value} (${value} / ${total})`);\n      });\n      break;\n    case 'count':\n      Object.entries(dict).forEach(([key, value]) => {\n        formattedOutput.push(`${key}: ${value}`);\n      });\n      break;\n  }\n  return formattedOutput;\n}\n\nexport function TopItemsList(\n  splitOperatorType: SplitOperatorType,\n  sortingMethod: SortingMethod,\n  displayFormat: DisplayFormat,\n  splitSeparator: string,\n  input: string | string[],\n  deleteEmptyItems: boolean,\n  ignoreItemCase: boolean,\n  trimItems: boolean\n): string {\n  if (!input) return '';\n\n  let array: string[];\n  if (typeof input === 'string') {\n    switch (splitOperatorType) {\n      case 'symbol':\n        array = input.split(splitSeparator);\n        break;\n      case 'regex':\n        array = input\n          .split(new RegExp(splitSeparator))\n          .filter((item) => item !== '');\n        break;\n    }\n  } else {\n    array = input;\n  }\n\n  // Trim items if required\n  if (trimItems) {\n    array = array.map((item) => item.trim());\n  }\n\n  // Delete empty items after initial split\n  if (deleteEmptyItems) {\n    array = array.filter((item) => item !== '');\n  }\n\n  // Transform the array into dict\n  const unsortedDict = itemCounter(array, ignoreItemCase);\n\n  // Sort the list if required\n  const sortedDict = dictSorter(unsortedDict, sortingMethod);\n\n  // Format the output with desired format\n  const formattedOutput = displayFormater(sortedDict, displayFormat);\n\n  return formattedOutput.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/list/find-unique/find-unique.service.test.ts",
    "content": "import { describe, expect } from 'vitest';\n\nimport { findUniqueCompute } from './service';\n\ndescribe('TopItemsList Function', () => {\n  test('should return unique items ignoring case sensitivity', () => {\n    const input = 'apple,banana,Apple,orange,Banana,apple';\n    const result = findUniqueCompute(\n      'symbol',\n      ',',\n      '\\n',\n      input,\n      true,\n      true,\n      false,\n      true\n    );\n    expect(result).toBe('orange');\n  });\n\n  test('should return unique items considering case sensitivity', () => {\n    const input = 'apple,banana,Apple,orange,Banana,apple';\n    const result = findUniqueCompute(\n      'symbol',\n      ',',\n      '\\n',\n      input,\n      true,\n      true,\n      true,\n      true\n    );\n    expect(result).toBe('banana\\nApple\\norange\\nBanana');\n  });\n\n  test('should return all unique items ignoring case sensitivity', () => {\n    const input = 'apple,banana,Apple,orange,Banana,apple';\n    const result = findUniqueCompute(\n      'symbol',\n      ',',\n      '\\n',\n      input,\n      true,\n      true,\n      false,\n      false\n    );\n    expect(result).toBe('apple\\nbanana\\norange');\n  });\n\n  test('should return all unique items considering case sensitivity', () => {\n    const input = 'apple,banana,Apple,orange,Banana,apple';\n    const result = findUniqueCompute(\n      'symbol',\n      ',',\n      '\\n',\n      input,\n      true,\n      true,\n      true,\n      false\n    );\n    expect(result).toBe('apple\\nbanana\\nApple\\norange\\nBanana');\n  });\n\n  test('should handle empty items deletion', () => {\n    const input = 'apple,,banana, ,orange';\n    const result = findUniqueCompute(\n      'symbol',\n      ',',\n      '\\n',\n      input,\n      true,\n      true,\n      false,\n      false\n    );\n    expect(result).toBe('apple\\nbanana\\norange');\n  });\n\n  test('should handle trimming items', () => {\n    const input = ' apple , banana , orange ';\n    const result = findUniqueCompute(\n      'symbol',\n      ',',\n      '\\n',\n      input,\n      false,\n      false,\n      false,\n      false\n    );\n    expect(result).toBe(' apple \\n banana \\n orange ');\n  });\n\n  test('should handle regex split', () => {\n    const input = 'apple banana orange';\n    const result = findUniqueCompute(\n      'regex',\n      '\\\\s+',\n      '\\n',\n      input,\n      false,\n      false,\n      false,\n      false\n    );\n    expect(result).toBe('apple\\nbanana\\norange');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/list/find-unique/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport ToolContent from '@components/ToolContent';\nimport { findUniqueCompute, SplitOperatorType } from './service';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  splitOperatorType: 'symbol' as SplitOperatorType,\n  splitSeparator: ',',\n  joinSeparator: '\\\\n',\n  deleteEmptyItems: true,\n  caseSensitive: false,\n  trimItems: true,\n  absolutelyUnique: false\n};\nconst splitOperators: {\n  title: string;\n  description: string;\n  type: SplitOperatorType;\n}[] = [\n  {\n    title: 'Use a Symbol for Splitting',\n    description: 'Delimit input list items with a character.',\n    type: 'symbol'\n  },\n  {\n    title: 'Use a Regex for Splitting',\n    type: 'regex',\n    description: 'Delimit input list items with a regular expression.'\n  }\n];\n\nexport default function FindUnique() {\n  const { t } = useTranslation('list');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n  const compute = (optionsValues: typeof initialValues, input: any) => {\n    const {\n      splitOperatorType,\n      splitSeparator,\n      joinSeparator,\n      deleteEmptyItems,\n      trimItems,\n      caseSensitive,\n      absolutelyUnique\n    } = optionsValues;\n\n    setResult(\n      findUniqueCompute(\n        splitOperatorType,\n        splitSeparator,\n        joinSeparator,\n        input,\n        deleteEmptyItems,\n        trimItems,\n        caseSensitive,\n        absolutelyUnique\n      )\n    );\n  };\n\n  return (\n    <ToolContent\n      title={t('findUnique.title')}\n      initialValues={initialValues}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('findUnique.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('findUnique.resultTitle')} value={result} />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('findUnique.inputListDelimiter'),\n          component: (\n            <Box>\n              {splitOperators.map(({ title, description, type }) => (\n                <SimpleRadio\n                  key={type}\n                  onClick={() => updateField('splitOperatorType', type)}\n                  title={title}\n                  description={description}\n                  checked={values.splitOperatorType === type}\n                />\n              ))}\n              <TextFieldWithDesc\n                description={t('findUnique.delimiterDescription')}\n                value={values.splitSeparator}\n                onOwnChange={(val) => updateField('splitSeparator', val)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('findUnique.outputListDelimiter'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                value={values.joinSeparator}\n                onOwnChange={(value) => updateField('joinSeparator', value)}\n              />\n              <CheckboxWithDesc\n                title={t('findUnique.trimItems')}\n                description={t('findUnique.trimItemsDescription')}\n                checked={values.trimItems}\n                onChange={(value) => updateField('trimItems', value)}\n              />\n              <CheckboxWithDesc\n                title={t('findUnique.skipEmptyItems')}\n                description={t('findUnique.skipEmptyItemsDescription')}\n                checked={values.deleteEmptyItems}\n                onChange={(value) => updateField('deleteEmptyItems', value)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('findUnique.uniqueItemOptions'),\n          component: (\n            <Box>\n              <CheckboxWithDesc\n                title={t('findUnique.findAbsolutelyUniqueItems')}\n                description={t(\n                  'findUnique.findAbsolutelyUniqueItemsDescription'\n                )}\n                checked={values.absolutelyUnique}\n                onChange={(value) => updateField('absolutelyUnique', value)}\n              />\n              <CheckboxWithDesc\n                title={t('findUnique.caseSensitiveItems')}\n                description={t('findUnique.caseSensitiveItemsDescription')}\n                checked={values.caseSensitive}\n                onChange={(value) => updateField('caseSensitive', value)}\n              />\n            </Box>\n          )\n        }\n      ]}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/list/find-unique/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('list', {\n  path: 'find-unique',\n  icon: 'material-symbols-light:search',\n\n  keywords: ['find', 'unique'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'list:findUnique.title',\n    description: 'list:findUnique.description',\n    shortDescription: 'list:findUnique.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/list/find-unique/service.ts",
    "content": "export type SplitOperatorType = 'symbol' | 'regex';\n\n// Function that builds the unique items array handling caseSensitive and absolutelyUnique options\nfunction uniqueListBuilder(\n  array: string[],\n  caseSensitive: boolean,\n  absolutelyUnique: boolean\n): string[] {\n  const dict: { [key: string]: number } = {};\n  for (const item of array) {\n    const key = caseSensitive ? item : item.toLowerCase();\n    dict[key] = (dict[key] || 0) + 1;\n  }\n  if (absolutelyUnique) {\n    for (const [key, value] of Object.entries(dict)) {\n      if (value > 1) {\n        delete dict[key];\n      }\n    }\n  }\n  return Object.keys(dict);\n}\n\nexport function findUniqueCompute(\n  splitOperatorType: SplitOperatorType,\n  splitSeparator: string,\n  joinSeparator: string = '\\n',\n  input: string,\n  deleteEmptyItems: boolean,\n  trimItems: boolean,\n  caseSensitive: boolean,\n  absolutelyUnique: boolean\n): string {\n  let array: string[];\n  switch (splitOperatorType) {\n    case 'symbol':\n      array = input.split(splitSeparator);\n      break;\n    case 'regex':\n      array = input\n        .split(new RegExp(splitSeparator))\n        .filter((item) => item !== '');\n      break;\n  }\n\n  // Trim items if required\n  if (trimItems) {\n    array = array.map((item) => item.trim());\n  }\n\n  // Delete empty items after initial split\n  if (deleteEmptyItems) {\n    array = array.filter((item) => item !== '');\n  }\n\n  // Format the output with desired format\n  const uniqueListItems = uniqueListBuilder(\n    array,\n    caseSensitive,\n    absolutelyUnique\n  );\n\n  return uniqueListItems.join(joinSeparator);\n}\n"
  },
  {
    "path": "src/pages/tools/list/group/group.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { chunkList, SplitOperatorType } from './service';\n\ndescribe('chunkList', () => {\n  it('splits by symbol, groups, pads, and formats correctly', () => {\n    const input = 'a,b,c,d,e,f,g,h,i,j';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ',';\n    const groupNumber = 3;\n    const itemSeparator = '-';\n    const leftWrap = '[';\n    const rightWrap = ']';\n    const groupSeparator = ' | ';\n    const deleteEmptyItems = false;\n    const padNonFullGroup = true;\n    const paddingChar = 'x';\n\n    const expectedOutput = '[a-b-c] | [d-e-f] | [g-h-i] | [j-x-x]';\n\n    const result = chunkList(\n      splitOperatorType,\n      splitSeparator,\n      input,\n      groupNumber,\n      itemSeparator,\n      leftWrap,\n      rightWrap,\n      groupSeparator,\n      deleteEmptyItems,\n      padNonFullGroup,\n      paddingChar\n    );\n\n    expect(result).toBe(expectedOutput);\n  });\n\n  it('handles regex split, no padding, and formats correctly', () => {\n    const input = 'a1b2c3d4e5f6g7h8i9j';\n    const splitOperatorType: SplitOperatorType = 'regex';\n    const splitSeparator = '\\\\d';\n    const groupNumber = 4;\n    const itemSeparator = ',';\n    const leftWrap = '(';\n    const rightWrap = ')';\n    const groupSeparator = ' / ';\n    const deleteEmptyItems = true;\n    const padNonFullGroup = false;\n\n    const expectedOutput = '(a,b,c,d) / (e,f,g,h) / (i,j)';\n\n    const result = chunkList(\n      splitOperatorType,\n      splitSeparator,\n      input,\n      groupNumber,\n      itemSeparator,\n      leftWrap,\n      rightWrap,\n      groupSeparator,\n      deleteEmptyItems,\n      padNonFullGroup\n    );\n\n    expect(result).toBe(expectedOutput);\n  });\n\n  it('handles empty items removal and padd the last group with a z', () => {\n    const input = 'a,,b,,c,,d,,e,,';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ',';\n    const groupNumber = 2;\n    const itemSeparator = ':';\n    const leftWrap = '<';\n    const rightWrap = '>';\n    const groupSeparator = ' & ';\n    const deleteEmptyItems = true;\n    const padNonFullGroup = true;\n    const paddingChar = 'z';\n\n    const expectedOutput = '<a:b> & <c:d> & <e:z>';\n\n    const result = chunkList(\n      splitOperatorType,\n      splitSeparator,\n      input,\n      groupNumber,\n      itemSeparator,\n      leftWrap,\n      rightWrap,\n      groupSeparator,\n      deleteEmptyItems,\n      padNonFullGroup,\n      paddingChar\n    );\n\n    expect(result).toBe(expectedOutput);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/list/group/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { chunkList, SplitOperatorType } from './service';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { formatNumber } from '../../../../utils/number';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  splitOperatorType: 'symbol' as SplitOperatorType,\n  splitSeparator: ',',\n  groupNumber: 2,\n  itemSeparator: ',',\n  leftWrap: '[',\n  rightWrap: ']',\n  groupSeparator: '\\\\n',\n  deleteEmptyItems: true,\n  padNonFullGroup: false,\n  paddingChar: '...'\n};\n\ntype InitialValuesType = typeof initialValues;\n\nconst splitOperators: SplitOperatorType[] = ['symbol', 'regex'];\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Group Hexagon Coordinates',\n    description:\n      'In this example, we group the coordinates of a regular hexagon. The input coordinates are given as a space-separated list \"x1 y1 x2 y2 x3 y3 …\". What we want to do is create vector point pairs such as \"(x1, y1); (x2, y2); (x3, y3); …\". To do that, we use the space character as the input coordinate separator, and to create vectors, we group them by pairs. We wrap the coordinates in parentheses, put a comma between the x and y group items, and a semicolon between individual groups.',\n    sampleText: '2.5 9.33 0 5 2.5 0.66 7.5 0.66 10 5 7.5 9.33',\n    sampleResult: `(2.5, 9.33); (0, 5); (2.5, 0.66); (7.5, 0.66); (10, 5); (7.5, 9.33)`,\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ' ',\n      groupNumber: 2,\n      itemSeparator: ', ',\n      leftWrap: '(',\n      rightWrap: ')',\n      groupSeparator: '; ',\n      deleteEmptyItems: true,\n      padNonFullGroup: false,\n      paddingChar: 'x'\n    }\n  },\n  {\n    title: 'Chunks of Size 3',\n    description:\n      'This example demonstrates grouping of list items and creates 9 groups of 3 items. The input list contains all alphabet letters (26 letters, separated by a semicolon) and the output is groups of letter trigrams. As the last group is missing one letter, we enable padding and add the underscore symbol as the padding element.',\n    sampleText: 'a;b;c;d;e;f;g;h;i;j;k;l;m;n;o;p;q;r;s;t;u;v;w;x;y;z',\n    sampleResult: `[a, b, c]\n[d, e, f]\n[g, h, i]\n[j, k, l]\n[m, n, o]\n[p, q, r]\n[s, t, u]\n[v, w, x]\n[y, z, _]`,\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ';',\n      groupNumber: 3,\n      itemSeparator: ',',\n      leftWrap: '[',\n      rightWrap: ']',\n      groupSeparator: '\\\\n',\n      deleteEmptyItems: false,\n      padNonFullGroup: true,\n      paddingChar: '_'\n    }\n  },\n  {\n    title: 'Convert a List to a TSV',\n    description:\n      'In this example, we use our list item grouper to convert a food list to tab-separated values (TSV). As spaces are chaotically used between the items of the input list, we use the item separating regular expression \"\\\\s+\" to match them. We create a TSV with three columns (three groups), separate them with a tab character, and put newlines between the groups.',\n    sampleText: `beef\t  buns\n  cake\t \tcorn\n crab\ndill  \nfish\n\tkiwi \tkale\n\n  lime  \tmeat\nmint\n   milk\n  pear\tplum\n\t  \tpate\n  pork\t   \trice  \nsoup\n  tuna   \n  tart`,\n    sampleResult: `beef\tbuns\tcake\ncorn\tcrab\tdill\nfish\tkiwi\tkale\nlime\tmeat\tmint\nmilk\tpear  plum\npate\tpork\trice\nsoup\ttuna\ttart`,\n    sampleOptions: {\n      splitOperatorType: 'regex',\n      splitSeparator: '\\\\s+',\n      groupNumber: 3,\n      itemSeparator: '\\\\t',\n      leftWrap: '',\n      rightWrap: '',\n      groupSeparator: '\\\\n',\n      deleteEmptyItems: true,\n      padNonFullGroup: false,\n      paddingChar: 'x'\n    }\n  }\n];\n\nexport default function ChunkList({ title }: ToolComponentProps) {\n  const { t } = useTranslation('list');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n  const compute = (optionsValues: typeof initialValues, input: any) => {\n    const {\n      splitOperatorType,\n      splitSeparator,\n      groupNumber,\n      itemSeparator,\n      leftWrap,\n      rightWrap,\n      groupSeparator,\n      deleteEmptyItems,\n      padNonFullGroup,\n      paddingChar\n    } = optionsValues;\n\n    setResult(\n      chunkList(\n        splitOperatorType,\n        splitSeparator,\n        input,\n        groupNumber,\n        itemSeparator,\n        leftWrap,\n        rightWrap,\n        groupSeparator,\n        deleteEmptyItems,\n        padNonFullGroup,\n        paddingChar\n      )\n    );\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      exampleCards={exampleCards}\n      inputComponent={\n        <ToolTextInput\n          title={t('chunk.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('chunk.resultTitle')} value={result} />\n      }\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('chunk.inputItemSeparator'),\n          component: (\n            <Box>\n              {splitOperators.map((type) => (\n                <SimpleRadio\n                  key={type}\n                  onClick={() => updateField('splitOperatorType', type)}\n                  title={t(`chunk.splitOperators.${type}.title`)}\n                  description={t(`chunk.splitOperators.${type}.description`)}\n                  checked={values.splitOperatorType === type}\n                />\n              ))}\n              <TextFieldWithDesc\n                description={t('chunk.splitSeparatorDescription')}\n                value={values.splitSeparator}\n                onOwnChange={(val) => updateField('splitSeparator', val)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('chunk.groupSizeAndSeparators'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                value={values.groupNumber}\n                description={t('chunk.groupNumberDescription')}\n                type={'number'}\n                onOwnChange={(value) =>\n                  updateField('groupNumber', formatNumber(value, 1))\n                }\n              />\n              <TextFieldWithDesc\n                value={values.itemSeparator}\n                description={t('chunk.itemSeparatorDescription')}\n                onOwnChange={(value) => updateField('itemSeparator', value)}\n              />\n              <TextFieldWithDesc\n                value={values.groupSeparator}\n                description={t('chunk.groupSeparatorDescription')}\n                onOwnChange={(value) => updateField('groupSeparator', value)}\n              />\n              <TextFieldWithDesc\n                value={values.leftWrap}\n                description={t('chunk.leftWrapDescription')}\n                onOwnChange={(value) => updateField('leftWrap', value)}\n              />\n              <TextFieldWithDesc\n                value={values.rightWrap}\n                description={t('chunk.rightWrapDescription')}\n                onOwnChange={(value) => updateField('rightWrap', value)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('chunk.emptyItemsAndPadding'),\n          component: (\n            <Box>\n              <CheckboxWithDesc\n                title={t('chunk.deleteEmptyItems')}\n                description={t('chunk.deleteEmptyItemsDescription')}\n                checked={values.deleteEmptyItems}\n                onChange={(value) => updateField('deleteEmptyItems', value)}\n              />\n              <CheckboxWithDesc\n                title={t('chunk.padNonFullGroups')}\n                description={t('chunk.padNonFullGroupsDescription')}\n                checked={values.padNonFullGroup}\n                onChange={(value) => updateField('padNonFullGroup', value)}\n              />\n              <TextFieldWithDesc\n                value={values.paddingChar}\n                description={t('chunk.paddingCharDescription')}\n                onOwnChange={(value) => updateField('paddingChar', value)}\n              />\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n      setInput={setInput}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/list/group/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('list', {\n  path: 'chunk',\n  icon: 'mdi:rhombus-split',\n\n  keywords: ['chuck', 'list', 'partition', 'split'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'list:chunk.title',\n    description: 'list:chunk.description',\n    shortDescription: 'list:chunk.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/list/group/service.ts",
    "content": "export type SplitOperatorType = 'symbol' | 'regex';\n\n// function that split the array into an array of subarray of desired length\nfunction chunkMaker(array: string[], chunkNumber: number): string[][] {\n  const result: string[][] = [];\n  for (let i = 0; i < array.length; i += chunkNumber) {\n    result.push(array.slice(i, i + chunkNumber));\n  }\n  return result;\n}\n\n// function use to handle the case paddingNonFullChunk is enable\nfunction chunkFiller(\n  array: string[][],\n  chunkNumber: number,\n  padNonFullChunk: boolean,\n  paddingChar: string = ''\n): string[][] {\n  if (padNonFullChunk) {\n    const lastSubArray: string[] = array[array.length - 1];\n    if (lastSubArray.length < chunkNumber) {\n      for (let i = lastSubArray.length; i < chunkNumber; i++) {\n        lastSubArray.push(paddingChar);\n      }\n    }\n    array[array.length - 1] = lastSubArray;\n  }\n  return array;\n}\n\n// function that join with the item separator and wrap with left and right each subArray of the Array\nfunction chunkJoinerAndWrapper(\n  array: string[][],\n  itemSeparator: string = '',\n  leftWrap: string = '',\n  rightWrap: string = ''\n): string[] {\n  return array.map((subArray) => {\n    return leftWrap + subArray.join(itemSeparator) + rightWrap;\n  });\n}\n\nexport function chunkList(\n  splitOperatorType: SplitOperatorType,\n  splitSeparator: string,\n  input: string,\n  chunkNumber: number,\n  itemSeparator: string = '',\n  leftWrap: string = '',\n  rightWrap: string = '',\n  chunkSeparator: string,\n  deleteEmptyItems: boolean,\n  padNonFullChunk: boolean,\n  paddingChar: string = ''\n): string {\n  if (!splitSeparator) return '';\n\n  let array: string[];\n\n  switch (splitOperatorType) {\n    case 'symbol':\n      array = input.split(splitSeparator);\n      break;\n    case 'regex':\n      array = input.split(new RegExp(splitSeparator));\n      break;\n  }\n  // delete empty items after intial split\n  if (deleteEmptyItems) {\n    array = array.filter((item) => item !== '');\n  }\n\n  // split the input into an array of subArray with the desired length\n  const splitedArray = chunkMaker(array, chunkNumber);\n\n  // fill the last subArray is PadNonFullChunk is enabled\n  const fullSplitedArray = chunkFiller(\n    splitedArray,\n    chunkNumber,\n    padNonFullChunk,\n    paddingChar\n  );\n\n  // get the list of formated subArray with the item separator and left and right wrapper\n  const result = chunkJoinerAndWrapper(\n    fullSplitedArray,\n    itemSeparator,\n    leftWrap,\n    rightWrap\n  );\n\n  // finnaly join the chunk separator before returning\n  return result.join(chunkSeparator);\n}\n"
  },
  {
    "path": "src/pages/tools/list/index.ts",
    "content": "import { tool as listDuplicate } from './duplicate/meta';\nimport { tool as listUnwrap } from './unwrap/meta';\nimport { tool as listReverse } from './reverse/meta';\nimport { tool as listFindUnique } from './find-unique/meta';\nimport { tool as listFindMostPopular } from './find-most-popular/meta';\nimport { tool as listGroup } from './group/meta';\nimport { tool as listWrap } from './wrap/meta';\nimport { tool as listRotate } from './rotate/meta';\nimport { tool as listTruncate } from './truncate/meta';\nimport { tool as listShuffle } from './shuffle/meta';\nimport { tool as listSort } from './sort/meta';\n\nexport const listTools = [\n  listSort,\n  listUnwrap,\n  listReverse,\n  listFindUnique,\n  listFindMostPopular,\n  listGroup,\n  listWrap,\n  listRotate,\n  listShuffle,\n  listTruncate,\n  listDuplicate\n];\n"
  },
  {
    "path": "src/pages/tools/list/reverse/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { reverseList, SplitOperatorType } from './service';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolContent from '@components/ToolContent';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  splitOperatorType: 'symbol' as SplitOperatorType,\n  splitSeparator: ',',\n  joinSeparator: '\\\\n'\n};\ntype InitialValuesType = typeof initialValues;\nconst splitOperators: {\n  title: string;\n  description: string;\n  type: SplitOperatorType;\n}[] = [\n  {\n    title: 'Use a Symbol for Splitting',\n    description: 'Delimit input list items with a character.',\n    type: 'symbol'\n  },\n  {\n    title: 'Use a Regex for Splitting',\n    type: 'regex',\n    description: 'Delimit input list items with a regular expression.'\n  }\n];\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Reverse a List of Digits',\n    description:\n      'In this example, we load a list of digits in the input. The digits are separated by a mix of dot, comma, and semicolon characters, so we use the regular expression split mode and enter a regular expression that matches all these characters as the input item separator. In the output, we get a reversed list of digits that all use the semicolon as a separator.',\n    sampleText: `2, 9, 6; 3; 7. 4. 4. 2, 1; 4, 8. 4; 4. 8, 2, 5; 1; 7; 7. 0`,\n    sampleResult: `0; 7; 7; 1; 5; 2; 8; 4; 4; 8; 4; 1; 2; 4; 4; 7; 3; 6; 9; 2`,\n    sampleOptions: {\n      splitOperatorType: 'regex',\n      splitSeparator: '[;,.]\\\\s*',\n      joinSeparator: '; '\n    }\n  },\n  {\n    title: 'Reverse a Column of Words',\n    description:\n      'This example reverses a column of twenty three-syllable nouns and prints all the words from the bottom to top. To separate the list items, it uses the \\n character as input item separator, which means that each item is on its own line..',\n    sampleText: `argument\npollution\nemphasis\nvehicle\nfamily\nproperty\npreference\nstudio\nsuggestion\naccident\nanalyst\npermission\nreaction\npromotion\nquantity\ninspection\nchemistry\nconclusion\nconfusion\nmemory`,\n    sampleResult: `memory\nconfusion\nconclusion\nchemistry\ninspection\nquantity\npromotion\nreaction\npermission\nanalyst\naccident\nsuggestion\nstudio\npreference\nproperty\nfamily\nvehicle\nemphasis\npollution\nargument`,\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: '\\\\n',\n      joinSeparator: '\\\\n'\n    }\n  },\n  {\n    title: 'Reverse a Random List',\n    description:\n      'In this example, the list elements are random cities, zip codes, and weather conditions. To reverse list elements, we first need to identify them and separate them apart. The input list incorrectly uses the dash symbol to separate the elements but the output list fixes this and uses commas.',\n    sampleText: `Hamburg-21334-Dhaka-Sunny-Managua-Rainy-Chongqing-95123-Oakland`,\n    sampleResult: `Oakland, 95123, Chongqing, Rainy, Managua, Sunny, Dhaka, 21334, Hamburg`,\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: '-',\n      joinSeparator: ', '\n    }\n  }\n];\n\nexport default function Reverse({ title }: ToolComponentProps) {\n  const { t } = useTranslation('list');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('reverse.splitterMode'),\n      component: (\n        <Box>\n          {splitOperators.map(({ title, description, type }) => (\n            <SimpleRadio\n              key={type}\n              onClick={() => updateField('splitOperatorType', type)}\n              title={t(`reverse.splitOperators.${type}.title`)}\n              description={t(`reverse.splitOperators.${type}.description`)}\n              checked={values.splitOperatorType === type}\n            />\n          ))}\n        </Box>\n      )\n    },\n    {\n      title: t('reverse.itemSeparator'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            description={t('reverse.itemSeparatorDescription')}\n            value={values.splitSeparator}\n            onOwnChange={(val) => updateField('splitSeparator', val)}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('reverse.outputListOptions'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            description={t('reverse.outputSeparatorDescription')}\n            value={values.joinSeparator}\n            onOwnChange={(val) => updateField('joinSeparator', val)}\n          />\n        </Box>\n      )\n    }\n  ];\n  const compute = (optionsValues: typeof initialValues, input: any) => {\n    const { splitOperatorType, splitSeparator, joinSeparator } = optionsValues;\n\n    setResult(\n      reverseList(splitOperatorType, splitSeparator, joinSeparator, input)\n    );\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('reverse.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('reverse.resultTitle')} value={result} />\n      }\n      toolInfo={{\n        title: t('reverse.toolInfo.title'),\n        description: t('reverse.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/list/reverse/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('list', {\n  path: 'reverse',\n  icon: 'proicons:reverse',\n  keywords: ['reverse'],\n  i18n: {\n    name: 'list:reverse.title',\n    description: 'list:reverse.description',\n    shortDescription: 'list:reverse.shortDescription',\n    userTypes: ['generalUsers']\n  },\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/list/reverse/reverse.service.test.ts",
    "content": "import { describe, expect } from 'vitest';\nimport { reverseList } from './service';\n\ndescribe('reverseList Function', () => {\n  test('should reverse items split by symbol', () => {\n    const input = 'apple,banana,orange';\n    const result = reverseList('symbol', ',', '\\n', input);\n    expect(result).toBe('orange\\nbanana\\napple');\n  });\n\n  test('should reverse items split by regex', () => {\n    const input = 'apple banana orange';\n    const result = reverseList('regex', '\\\\s+', '\\n', input);\n    expect(result).toBe('orange\\nbanana\\napple');\n  });\n\n  test('should handle empty input', () => {\n    const input = '';\n    const result = reverseList('symbol', ',', '\\n', input);\n    expect(result).toBe('');\n  });\n\n  test('should handle join separator', () => {\n    const input = 'apple,banana,orange';\n    const result = reverseList('symbol', ',', ', ', input);\n    expect(result).toBe('orange, banana, apple');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/list/reverse/service.ts",
    "content": "export type SplitOperatorType = 'symbol' | 'regex';\n\nexport function reverseList(\n  splitOperatorType: SplitOperatorType,\n  splitSeparator: string,\n  joinSeparator: string = '\\n',\n  input: string\n): string {\n  let array: string[] = [];\n  switch (splitOperatorType) {\n    case 'symbol':\n      array = input.split(splitSeparator);\n      break;\n    case 'regex':\n      array = input\n        .split(new RegExp(splitSeparator))\n        .filter((item) => item !== '');\n      break;\n  }\n\n  const reversedList = array.reverse();\n  return reversedList.join(joinSeparator);\n}\n"
  },
  {
    "path": "src/pages/tools/list/rotate/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { rotateList, SplitOperatorType } from './service';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { formatNumber } from '../../../../utils/number';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\n\nconst initialValues = {\n  splitOperatorType: 'symbol' as SplitOperatorType,\n  input: '',\n  splitSeparator: ',',\n  joinSeparator: ',',\n  right: true,\n  step: 1\n};\nconst splitOperators: {\n  title: string;\n  description: string;\n  type: SplitOperatorType;\n}[] = [\n  {\n    title: 'Use a Symbol for Splitting',\n    description: 'Delimit input list items with a character.',\n    type: 'symbol'\n  },\n  {\n    title: 'Use a Regex for Splitting',\n    type: 'regex',\n    description: 'Delimit input list items with a regular expression.'\n  }\n];\nconst rotationDirections: {\n  title: string;\n  description: string;\n  value: boolean;\n}[] = [\n  {\n    title: 'Rotate forward',\n    description:\n      'Rotate list items to the right. (Down if a vertical column list.)',\n    value: true\n  },\n  {\n    title: 'Rotate backward',\n    description:\n      'Rotate list items to the left. (Up if a vertical column list.)',\n    value: false\n  }\n];\n\nexport default function Rotate({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n  const compute = (optionsValues: typeof initialValues, input: any) => {\n    const { splitOperatorType, splitSeparator, joinSeparator, right, step } =\n      optionsValues;\n\n    setResult(\n      rotateList(\n        splitOperatorType,\n        input,\n        splitSeparator,\n        joinSeparator,\n        right,\n        step\n      )\n    );\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput title={'Input list'} value={input} onChange={setInput} />\n      }\n      resultComponent={<ToolTextResult title={'Rotated list'} value={result} />}\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: 'Item split mode',\n          component: (\n            <Box>\n              {splitOperators.map(({ title, description, type }) => (\n                <SimpleRadio\n                  key={type}\n                  onClick={() => updateField('splitOperatorType', type)}\n                  title={title}\n                  description={description}\n                  checked={values.splitOperatorType === type}\n                />\n              ))}\n              <TextFieldWithDesc\n                description={'Set a delimiting symbol or regular expression.'}\n                value={values.splitSeparator}\n                onOwnChange={(val) => updateField('splitSeparator', val)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: 'Rotation Direction and Count',\n          component: (\n            <Box>\n              {rotationDirections.map(({ title, description, value }) => (\n                <SimpleRadio\n                  key={`${value}`}\n                  onClick={() => updateField('right', value)}\n                  title={title}\n                  description={description}\n                  checked={values.right === value}\n                />\n              ))}\n              <TextFieldWithDesc\n                description={'Number of items to rotate'}\n                value={values.step}\n                onOwnChange={(val) => updateField('step', formatNumber(val, 1))}\n              />\n            </Box>\n          )\n        },\n        {\n          title: 'Rotated List Joining Symbol',\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                value={values.joinSeparator}\n                onOwnChange={(value) => updateField('joinSeparator', value)}\n                description={\n                  'Enter the character that goes between items in the rotated list.'\n                }\n              />\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n      setInput={setInput}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/list/rotate/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('list', {\n  path: 'rotate',\n  icon: 'material-symbols-light:rotate-right',\n\n  keywords: ['rotate'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'list:rotate.title',\n    description: 'list:rotate.description',\n    shortDescription: 'list:rotate.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/list/rotate/rotate.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { rotateList, SplitOperatorType } from './service';\n\ndescribe('rotate function', () => {\n  it('should rotate right side if right is set to true', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = '  ';\n    const step = 1;\n    const right = true;\n\n    const result = rotateList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      right,\n\n      step\n    );\n\n    expect(result).toBe('mango  apple  pineaple  lemon  orange');\n  });\n\n  it('should rotate left side if right is set to true', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const step = 1;\n    const right = false;\n\n    const result = rotateList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      right,\n\n      step\n    );\n\n    expect(result).toBe('pineaple lemon orange mango apple');\n  });\n\n  it('should rotate left side with 2 step if right is set to true', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const step = 2;\n    const right = false;\n\n    const result = rotateList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      right,\n\n      step\n    );\n\n    expect(result).toBe('lemon orange mango apple pineaple');\n  });\n\n  it('should raise an error if step is negative', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const step = -2;\n    const right = false;\n\n    expect(() => {\n      rotateList(\n        splitOperatorType,\n        input,\n        splitSeparator,\n        joinSeparator,\n        right,\n        step\n      );\n    }).toThrowError('Rotation step must be greater than zero.');\n  });\n\n  it('should raise an error if step is undefined', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const right = false;\n\n    expect(() => {\n      rotateList(\n        splitOperatorType,\n        input,\n        splitSeparator,\n        joinSeparator,\n        right\n      );\n    }).toThrowError('Rotation step contains non-digits.');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/list/rotate/service.ts",
    "content": "export type SplitOperatorType = 'symbol' | 'regex';\n\nfunction rotateArray(array: string[], step: number, right: boolean): string[] {\n  const length = array.length;\n\n  // Normalize the step to be within the bounds of the array length\n  const normalizedPositions = ((step % length) + length) % length;\n\n  if (right) {\n    // Rotate right\n    return array\n      .slice(-normalizedPositions)\n      .concat(array.slice(0, -normalizedPositions));\n  } else {\n    // Rotate left\n    return array\n      .slice(normalizedPositions)\n      .concat(array.slice(0, normalizedPositions));\n  }\n}\n\nexport function rotateList(\n  splitOperatorType: SplitOperatorType,\n  input: string,\n  splitSeparator: string,\n  joinSeparator: string,\n  right: boolean,\n  step?: number\n): string {\n  let array: string[];\n  let rotatedArray: string[];\n  switch (splitOperatorType) {\n    case 'symbol':\n      array = input.split(splitSeparator);\n      break;\n    case 'regex':\n      array = input.split(new RegExp(splitSeparator));\n      break;\n  }\n  if (step !== undefined) {\n    if (step <= 0) {\n      throw new Error('Rotation step must be greater than zero.');\n    }\n    rotatedArray = rotateArray(array, step, right);\n    return rotatedArray.join(joinSeparator);\n  }\n  throw new Error('Rotation step contains non-digits.');\n}\n"
  },
  {
    "path": "src/pages/tools/list/shuffle/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport ToolContent from '@components/ToolContent';\nimport { shuffleList, SplitOperatorType } from './service';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { isNumber } from '@utils/string';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  splitOperatorType: 'symbol' as SplitOperatorType,\n  splitSeparator: ',',\n  joinSeparator: ',',\n  length: ''\n};\nconst splitOperators: {\n  title: string;\n  description: string;\n  type: SplitOperatorType;\n}[] = [\n  {\n    title: 'Use a Symbol for Splitting',\n    description: 'Delimit input list items with a character.',\n    type: 'symbol'\n  },\n  {\n    title: 'Use a Regex for Splitting',\n    type: 'regex',\n    description: 'Delimit input list items with a regular expression.'\n  }\n];\n\nexport default function Shuffle() {\n  const { t } = useTranslation('list');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n  const compute = (optionsValues: typeof initialValues, input: any) => {\n    const { splitOperatorType, splitSeparator, joinSeparator, length } =\n      optionsValues;\n\n    setResult(\n      shuffleList(\n        splitOperatorType,\n        input,\n        splitSeparator,\n        joinSeparator,\n        isNumber(length) ? Number(length) : undefined\n      )\n    );\n  };\n\n  return (\n    <ToolContent\n      title={t('shuffle.title')}\n      initialValues={initialValues}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('shuffle.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('shuffle.resultTitle')} value={result} />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('shuffle.inputListSeparator'),\n          component: (\n            <Box>\n              {splitOperators.map(({ title, description, type }) => (\n                <SimpleRadio\n                  key={type}\n                  onClick={() => updateField('splitOperatorType', type)}\n                  title={title}\n                  description={description}\n                  checked={values.splitOperatorType === type}\n                />\n              ))}\n              <TextFieldWithDesc\n                description={t('shuffle.delimiterDescription')}\n                value={values.splitSeparator}\n                onOwnChange={(val) => updateField('splitSeparator', val)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('shuffle.shuffledListLength'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                description={t('shuffle.outputLengthDescription')}\n                value={values.length}\n                onOwnChange={(val) => updateField('length', val)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('shuffle.shuffledListSeparator'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                value={values.joinSeparator}\n                onOwnChange={(value) => updateField('joinSeparator', value)}\n                description={t('shuffle.joinSeparatorDescription')}\n              />\n            </Box>\n          )\n        }\n      ]}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/list/shuffle/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('list', {\n  path: 'shuffle',\n  icon: 'material-symbols-light:shuffle',\n\n  keywords: ['shuffle'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'list:shuffle.title',\n    description: 'list:shuffle.description',\n    shortDescription: 'list:shuffle.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/list/shuffle/service.ts",
    "content": "export type SplitOperatorType = 'symbol' | 'regex';\n\n// function that randomize the array\nfunction shuffleArray(array: string[]): string[] {\n  const shuffledArray = array.slice(); // Create a copy of the array\n  for (let i = shuffledArray.length - 1; i > 0; i--) {\n    const j = Math.floor(Math.random() * (i + 1));\n    [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];\n  }\n  return shuffledArray;\n}\n\nexport function shuffleList(\n  splitOperatorType: SplitOperatorType,\n  input: string,\n  splitSeparator: string,\n  joinSeparator: string,\n  length?: number //  \"?\" is to handle the case the user let the input blank\n): string {\n  let array: string[];\n  let shuffledArray: string[];\n  switch (splitOperatorType) {\n    case 'symbol':\n      array = input.split(splitSeparator);\n      break;\n    case 'regex':\n      array = input.split(new RegExp(splitSeparator));\n      break;\n  }\n  shuffledArray = shuffleArray(array);\n  if (length !== undefined) {\n    if (length <= 0) {\n      throw new Error('Length value must be a positive number.');\n    }\n    return shuffledArray.slice(0, length).join(joinSeparator);\n  }\n  return shuffledArray.join(joinSeparator);\n}\n"
  },
  {
    "path": "src/pages/tools/list/shuffle/shuffle.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { shuffleList, SplitOperatorType } from './service';\n\ndescribe('shuffle function', () => {\n  it('should be a 4 length list if no length value defined ', () => {\n    const input: string = 'apple, pineaple, lemon, orange';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n\n    const result = shuffleList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator\n    );\n    expect(result.split(joinSeparator).length).toBe(4);\n  });\n\n  it('should be a 2 length list if length value is set to 2', () => {\n    const input: string = 'apple, pineaple, lemon, orange';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const length = 2;\n\n    const result = shuffleList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      length\n    );\n    expect(result.split(joinSeparator).length).toBe(2);\n  });\n\n  it('should be a 4 length list if length value is set to 99', () => {\n    const input: string = 'apple, pineaple, lemon, orange';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const length = 99;\n\n    const result = shuffleList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      length\n    );\n    expect(result.split(joinSeparator).length).toBe(4);\n  });\n\n  it('should include a random element  if length value is undefined', () => {\n    const input: string = 'apple, pineaple, lemon, orange';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n\n    const result = shuffleList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      length\n    );\n    expect(result.split(joinSeparator)).toContain('apple');\n  });\n\n  it('should return empty string if input is empty', () => {\n    const input: string = '';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n\n    const result = shuffleList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      length\n    );\n    expect(result).toBe('');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/list/sort/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { Sort, SortingMethod, SplitOperatorType } from './service';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  splitSeparatorType: 'symbol' as SplitOperatorType,\n  sortingMethod: 'alphabetic' as SortingMethod,\n  increasing: true,\n  splitSeparator: ',',\n  joinSeparator: ',',\n  removeDuplicated: false,\n  caseSensitive: false\n};\nconst splitOperators: {\n  title: string;\n  description: string;\n  type: SplitOperatorType;\n}[] = [\n  {\n    title: 'Use a Symbol for Splitting',\n    description: 'Delimit input list items with a character.',\n    type: 'symbol'\n  },\n  {\n    title: 'Use a Regex for Splitting',\n    type: 'regex',\n    description: 'Delimit input list items with a regular expression.'\n  }\n];\n\nexport default function SortList({ title }: ToolComponentProps) {\n  const { t } = useTranslation('list');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n  const compute = (optionsValues: typeof initialValues, input: any) => {\n    const {\n      splitSeparatorType,\n      joinSeparator,\n      splitSeparator,\n      increasing,\n      caseSensitive,\n      removeDuplicated,\n      sortingMethod\n    } = optionsValues;\n\n    setResult(\n      Sort(\n        sortingMethod,\n        splitSeparatorType,\n        input,\n        increasing,\n        splitSeparator,\n        joinSeparator,\n        removeDuplicated,\n        caseSensitive\n      )\n    );\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput\n          title={t('sort.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('sort.resultTitle')} value={result} />\n      }\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('sort.inputItemSeparator'),\n          component: (\n            <Box>\n              {splitOperators.map(({ title, description, type }) => (\n                <SimpleRadio\n                  key={type}\n                  onClick={() => updateField('splitSeparatorType', type)}\n                  title={t(`sort.splitOperators.${type}.title`)}\n                  description={t(`sort.splitOperators.${type}.description`)}\n                  checked={values.splitSeparatorType === type}\n                />\n              ))}\n              <TextFieldWithDesc\n                description={t('sort.splitSeparatorDescription')}\n                value={values.splitSeparator}\n                onOwnChange={(val) => updateField('splitSeparator', val)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('sort.sortMethod'),\n          component: (\n            <Box>\n              <SelectWithDesc\n                selected={values.sortingMethod}\n                options={[\n                  {\n                    label: t('sort.sortOptions.alphabetic'),\n                    value: 'alphabetic'\n                  },\n                  {\n                    label: t('sort.sortOptions.numeric'),\n                    value: 'numeric'\n                  },\n                  { label: t('sort.sortOptions.length'), value: 'length' }\n                ]}\n                onChange={(value) => updateField('sortingMethod', value)}\n                description={t('sort.sortMethodDescription')}\n              />\n              <SelectWithDesc\n                selected={values.increasing}\n                options={[\n                  {\n                    label: t('sort.orderOptions.increasing'),\n                    value: true\n                  },\n                  {\n                    label: t('sort.orderOptions.decreasing'),\n                    value: false\n                  }\n                ]}\n                onChange={(value) => {\n                  updateField('increasing', value);\n                }}\n                description={t('sort.orderDescription')}\n              />\n              <CheckboxWithDesc\n                title={t('sort.caseSensitive')}\n                description={t('sort.caseSensitiveDescription')}\n                checked={values.caseSensitive}\n                onChange={(val) => updateField('caseSensitive', val)}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('sort.sortedItemProperties'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                description={t('sort.joinSeparatorDescription')}\n                value={values.joinSeparator}\n                onOwnChange={(val) => updateField('joinSeparator', val)}\n              />\n              <CheckboxWithDesc\n                title={t('sort.removeDuplicates')}\n                description={t('sort.removeDuplicatesDescription')}\n                checked={values.removeDuplicated}\n                onChange={(val) => updateField('removeDuplicated', val)}\n              />\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n      setInput={setInput}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/list/sort/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('list', {\n  path: 'sort',\n  icon: 'material-symbols-light:sort',\n\n  keywords: ['sort'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'list:sort.title',\n    description: 'list:sort.description',\n    shortDescription: 'list:sort.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/list/sort/service.ts",
    "content": "import { isNumber } from 'utils/string';\n\nexport type SortingMethod = 'numeric' | 'alphabetic' | 'length';\nexport type SplitOperatorType = 'symbol' | 'regex';\n\n// utils function that choose the way of numeric sorting mixed types of array\nfunction customNumericSort(a: string, b: string, increasing: boolean): number {\n  const formattedA = isNumber(a) ? Number(a) : a;\n  const formattedB = isNumber(b) ? Number(b) : b;\n  if (typeof formattedA === 'number' && typeof formattedB === 'number') {\n    return increasing ? formattedA - formattedB : formattedB - formattedA;\n  } else if (typeof formattedA === 'string' && typeof formattedB === 'string') {\n    return formattedA.localeCompare(formattedB); // Lexicographical comparison for strings\n  } else if (typeof formattedA === 'number' && typeof formattedB === 'string') {\n    return -1; // Numbers before strings\n  } else {\n    return 1; // Strings after numbers\n  }\n}\n\nexport function numericSort(\n  array: string[], // array we build after parsing the input\n  increasing: boolean,\n  joinSeparator: string,\n  removeDuplicated: boolean // the value if the checkbox has been selected 1 else 0\n) {\n  array.sort((a, b) => customNumericSort(a, b, increasing));\n  if (removeDuplicated) {\n    array = array.filter((item, index) => array.indexOf(item) === index);\n  }\n  return array.join(joinSeparator);\n}\n\n// utils function that choose the way of numeric sorting mixed types of array\nfunction customLengthSort(a: string, b: string, increasing: boolean): number {\n  return increasing ? a.length - b.length : b.length - a.length;\n}\n\nexport function lengthSort(\n  array: string[], // array we build after parsing the input\n  increasing: boolean, // select value has to be increasing for increasing order and decreasing for decreasing order\n  joinSeparator: string,\n  removeDuplicated: boolean // the value if the checkbox has been selected 1 else 0\n) {\n  array.sort((a, b) => customLengthSort(a, b, increasing));\n  if (removeDuplicated) {\n    array = array.filter((item, index) => array.indexOf(item) === index);\n  }\n  return array.join(joinSeparator);\n}\n\n// Utils function that chooses the way of alphabetic sorting mixed types of array\nfunction customAlphabeticSort(\n  a: string,\n  b: string,\n  caseSensitive: boolean\n): number {\n  if (!caseSensitive) {\n    // Case-insensitive comparison\n    return a.toLowerCase().localeCompare(b.toLowerCase());\n  } else {\n    // Case-sensitive comparison\n    return a.charCodeAt(0) - b.charCodeAt(0);\n  }\n}\n\nexport function alphabeticSort(\n  array: string[], // array we build after parsing the input\n  increasing: boolean, // select value has to be \"increasing\" for increasing order and \"decreasing\" for decreasing order\n  joinSeparator: string,\n  removeDuplicated: boolean, // the value if the checkbox has been selected 1 else 0\n  caseSensitive: boolean // the value if the checkbox has been selected 1 else 0\n) {\n  array.sort((a, b) => customAlphabeticSort(a, b, caseSensitive));\n  if (!increasing) {\n    array.reverse();\n  }\n  if (removeDuplicated) {\n    array = array.filter((item, index) => array.indexOf(item) === index);\n  }\n  return array.join(joinSeparator);\n}\n\n// main function\nexport function Sort(\n  sortingMethod: SortingMethod,\n  splitOperatorType: SplitOperatorType,\n  input: string,\n  increasing: boolean,\n  splitSeparator: string,\n  joinSeparator: string,\n  removeDuplicated: boolean,\n  caseSensitive: boolean\n) {\n  let array: string[];\n  switch (splitOperatorType) {\n    case 'symbol':\n      array = input.split(splitSeparator);\n      break;\n    case 'regex':\n      array = input.split(new RegExp(splitSeparator));\n      break;\n  }\n  let result: string;\n  switch (sortingMethod) {\n    case 'numeric':\n      result = numericSort(array, increasing, joinSeparator, removeDuplicated);\n      break;\n    case 'length':\n      result = lengthSort(array, increasing, joinSeparator, removeDuplicated);\n      break;\n    case 'alphabetic':\n      result = alphabeticSort(\n        array,\n        increasing,\n        joinSeparator,\n        removeDuplicated,\n        caseSensitive\n      );\n      break;\n  }\n  return result;\n}\n"
  },
  {
    "path": "src/pages/tools/list/sort/sort.service.test.ts",
    "content": "// Import necessary modules and functions\nimport { describe, expect, it } from 'vitest';\nimport {\n  alphabeticSort,\n  lengthSort,\n  numericSort,\n  Sort,\n  SortingMethod,\n  SplitOperatorType\n} from './service';\n\n// Define test cases for the numericSort function\ndescribe('numericSort function', () => {\n  it('should sort a list in increasing order with comma separator not removeduplicated elements', () => {\n    const array: string[] = ['9', '8', '7', '4', '2', '2', '5'];\n    const increasing: boolean = true;\n    const separator = ', ';\n    const removeDuplicated: boolean = false;\n\n    const result = numericSort(array, increasing, separator, removeDuplicated);\n    expect(result).toBe('2, 2, 4, 5, 7, 8, 9');\n  });\n\n  it('should sort a list in decreasing order with \" - \" separator and remove duplicated elements', () => {\n    const array: string[] = ['2', '4', '4', '9', '6', '6', '7'];\n    const increasing: boolean = false;\n    const separator = ' - ';\n    const removeDuplicated: boolean = true;\n\n    const result = numericSort(array, increasing, separator, removeDuplicated);\n    expect(result).toBe('9 - 7 - 6 - 4 - 2');\n  });\n\n  it('should sort a list with numbers and characters and remove duplicated elements', () => {\n    const array: string[] = ['d', 'd', 'n', 'p', 'h', 'h', '6', '9', '7', '5'];\n    const increasing: boolean = true;\n    const separator = ' ';\n    const removeDuplicated: boolean = true;\n\n    const result = numericSort(array, increasing, separator, removeDuplicated);\n    expect(result).toBe('5 6 7 9 d h n p');\n  });\n\n  // Define test cases for the lengthSort function\n  describe('lengthSort function', () => {\n    it('should sort a list of number by length in increasing order with comma separator ', () => {\n      const array: string[] = ['415689521', '3', '126', '12', '1523'];\n      const increasing: boolean = true;\n      const separator = ', ';\n      const removeDuplicated: boolean = false;\n\n      const result = lengthSort(array, increasing, separator, removeDuplicated);\n      expect(result).toBe('3, 12, 126, 1523, 415689521');\n    });\n\n    it('should sort a list of number by length in increasing order and remove duplicated elements ', () => {\n      const array: string[] = [\n        '415689521',\n        '3',\n        '3',\n        '126',\n        '12',\n        '12',\n        '1523'\n      ];\n      const increasing: boolean = true;\n      const separator = ', ';\n      const removeDuplicated: boolean = true;\n\n      const result = lengthSort(array, increasing, separator, removeDuplicated);\n      expect(result).toBe('3, 12, 126, 1523, 415689521');\n    });\n\n    it('should sort a mixed array by length in increasing order ', () => {\n      const array: string[] = [\n        'ddd',\n        'd',\n        'nfg',\n        'p',\n        'h',\n        'h',\n        '6555',\n        '9',\n        '7',\n        '5556'\n      ];\n      const increasing: boolean = true;\n      const separator = ' ';\n      const removeDuplicated: boolean = true;\n\n      const result = lengthSort(array, increasing, separator, removeDuplicated);\n      expect(result).toBe('d p h 9 7 ddd nfg 6555 5556');\n    });\n  });\n\n  // Define test cases for the alphabeticSort function\n  describe('alphabeticSort function', () => {\n    // NON CASE SENSITIVE TEST\n    it('should sort a list of string in increasing order with comma separator ', () => {\n      const array: string[] = ['apple', 'pineaple', 'lemon', 'orange'];\n      const increasing: boolean = true;\n      const separator = ', ';\n      const removeDuplicated: boolean = false;\n      const caseSensitive: boolean = false;\n\n      const result = alphabeticSort(\n        array,\n        increasing,\n        separator,\n        removeDuplicated,\n        caseSensitive\n      );\n      expect(result).toBe('apple, lemon, orange, pineaple');\n    });\n\n    it('should sort a list of string in decreasing order with comma separator ', () => {\n      const array: string[] = ['apple', 'pineaple', 'lemon', 'orange'];\n      const increasing: boolean = false;\n      const separator = ', ';\n      const removeDuplicated: boolean = false;\n      const caseSensitive: boolean = false;\n\n      const result = alphabeticSort(\n        array,\n        increasing,\n        separator,\n        removeDuplicated,\n        caseSensitive\n      );\n      expect(result).toBe('pineaple, orange, lemon, apple');\n    });\n\n    it('should sort a list of string and symbols (uppercase and lower) in increasing order with comma separator ', () => {\n      const array: string[] = [\n        'Apple',\n        'pineaple',\n        'lemon',\n        'Orange',\n        '1',\n        '9',\n        '@',\n        '+'\n      ];\n      const increasing: boolean = true;\n      const separator = ' ';\n      const removeDuplicated: boolean = true;\n      const caseSensitive: boolean = false;\n\n      const result = alphabeticSort(\n        array,\n        increasing,\n        separator,\n        removeDuplicated,\n        caseSensitive\n      );\n      expect(result).toBe('@ + 1 9 Apple lemon Orange pineaple');\n    });\n\n    it('should sort a list of string and symbols (uppercase and lower) in decreasing order with comma separator ', () => {\n      const array: string[] = [\n        'Apple',\n        'pineaple',\n        'lemon',\n        'Orange',\n        '1',\n        '9',\n        '@',\n        '+'\n      ];\n      const increasing: boolean = false;\n      const separator = ' ';\n      const removeDuplicated: boolean = true;\n      const caseSensitive: boolean = false;\n\n      const result = alphabeticSort(\n        array,\n        increasing,\n        separator,\n        removeDuplicated,\n        caseSensitive\n      );\n      expect(result).toBe('pineaple Orange lemon Apple 9 1 + @');\n    });\n\n    // CASE SENSITIVE TEST\n    it('should sort a list of string (uppercase) in decreasing order with comma separator ', () => {\n      const array: string[] = ['Apple', 'Pineaple', 'Lemon', 'Orange'];\n      const increasing: boolean = false;\n      const separator = ' ';\n      const removeDuplicated: boolean = false;\n      const caseSensitive: boolean = true;\n\n      const result = alphabeticSort(\n        array,\n        increasing,\n        separator,\n        removeDuplicated,\n        caseSensitive\n      );\n      expect(result).toBe('Pineaple Orange Lemon Apple');\n    });\n\n    it('should sort a list of string (uppercase and lowercase) in increasing order with comma separator ', () => {\n      const array: string[] = [\n        'Apple',\n        'pineaple',\n        'lemon',\n        'Orange',\n        '1',\n        '9'\n      ];\n      const increasing: boolean = true;\n      const separator = ' ';\n      const removeDuplicated: boolean = true;\n      const caseSensitive: boolean = true;\n\n      const result = alphabeticSort(\n        array,\n        increasing,\n        separator,\n        removeDuplicated,\n        caseSensitive\n      );\n      expect(result).toBe('1 9 Apple Orange lemon pineaple');\n    });\n\n    it('should sort a list of string (uppercase and lower) in decreasing order with comma separator ', () => {\n      const array: string[] = [\n        'Apple',\n        'pineaple',\n        'lemon',\n        'Orange',\n        '1',\n        '9'\n      ];\n      const increasing: boolean = false;\n      const separator = ' ';\n      const removeDuplicated: boolean = true;\n      const caseSensitive: boolean = true;\n\n      const result = alphabeticSort(\n        array,\n        increasing,\n        separator,\n        removeDuplicated,\n        caseSensitive\n      );\n      expect(result).toBe('pineaple lemon Orange Apple 9 1');\n    });\n\n    it('should sort a list of string and symbols (uppercase and lower) in decreasing order with comma separator ', () => {\n      const array: string[] = [\n        'Apple',\n        'pineaple',\n        'lemon',\n        'Orange',\n        '1',\n        '9',\n        '@',\n        '+'\n      ];\n      const increasing: boolean = true;\n      const separator = ' ';\n      const removeDuplicated: boolean = true;\n      const caseSensitive: boolean = true;\n\n      const result = alphabeticSort(\n        array,\n        increasing,\n        separator,\n        removeDuplicated,\n        caseSensitive\n      );\n      expect(result).toBe('+ 1 9 @ Apple Orange lemon pineaple');\n    });\n\n    it('should sort a list of string and symbols (uppercase and lower) in decreasing order with comma separator ', () => {\n      const array: string[] = [\n        'Apple',\n        'pineaple',\n        'lemon',\n        'Orange',\n        '1',\n        '9',\n        '@',\n        '+'\n      ];\n      const increasing: boolean = false;\n      const separator = ' ';\n      const removeDuplicated: boolean = true;\n      const caseSensitive: boolean = true;\n\n      const result = alphabeticSort(\n        array,\n        increasing,\n        separator,\n        removeDuplicated,\n        caseSensitive\n      );\n      expect(result).toBe('pineaple lemon Orange Apple @ 9 1 +');\n    });\n  });\n\n  // Define test cases for the lengthSort function\n  describe('main function', () => {\n    it('should do everything alph', () => {\n      const sortingMethod: SortingMethod = 'alphabetic';\n      const splitOperatorType: SplitOperatorType = 'symbol';\n      const input: string = 'Apple pineaple lemon Orange 1 9 @ +';\n      const increasing: boolean = true;\n      const splitSeparator: string = ' ';\n      const joinSeparator: string = ' ';\n      const removeDuplicated: boolean = true;\n      const caseSensitive: boolean = true;\n\n      const result = Sort(\n        sortingMethod,\n        splitOperatorType,\n        input,\n        increasing,\n        splitSeparator,\n        joinSeparator,\n        removeDuplicated,\n        caseSensitive\n      );\n      expect(result).toBe('+ 1 9 @ Apple Orange lemon pineaple');\n    });\n\n    it('should do everything numeric', () => {\n      const sortingMethod: SortingMethod = 'numeric';\n      const splitOperatorType: SplitOperatorType = 'symbol';\n      const input: string = '1 6 9 4 6 7 3 5 8';\n      const increasing: boolean = true;\n      const splitSeparator: string = ' ';\n      const joinSeparator: string = ' ';\n      const removeDuplicated: boolean = true;\n      const caseSensitive: boolean = true;\n\n      const result = Sort(\n        sortingMethod,\n        splitOperatorType,\n        input,\n        increasing,\n        splitSeparator,\n        joinSeparator,\n        removeDuplicated,\n        caseSensitive\n      );\n      expect(result).toBe('1 3 4 5 6 7 8 9');\n    });\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/list/truncate/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Box } from '@mui/material';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { SplitOperatorType, truncateList } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport * as Yup from 'yup';\n\ninterface InitialValuesType {\n  splitOperatorType: SplitOperatorType;\n  splitSeparator: string;\n  joinSeparator: string;\n  end: boolean;\n  length: string;\n}\n\nconst initialValues: InitialValuesType = {\n  splitOperatorType: 'symbol',\n  splitSeparator: ',',\n  joinSeparator: ',',\n  end: true,\n  length: '3'\n};\n\nconst validationSchema = Yup.object({\n  splitSeparator: Yup.string().required('The separator is required'),\n  joinSeparator: Yup.string().required('The join separator is required'),\n  length: Yup.number()\n    .typeError('Length must be a number')\n    .min(0, 'Length must be a positive number')\n    .required('Length is required')\n});\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Keep first 3 items in a list',\n    description:\n      'This example shows how to keep only the first 3 items in a comma-separated list.',\n    sampleText: 'apple, pineapple, lemon, orange, mango',\n    sampleResult: 'apple,pineapple,lemon',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ', ',\n      joinSeparator: ',',\n      end: true,\n      length: '3'\n    }\n  },\n  {\n    title: 'Keep last 2 items in a list',\n    description:\n      'This example shows how to keep only the last 2 items in a comma-separated list.',\n    sampleText: 'apple, pineapple, lemon, orange, mango',\n    sampleResult: 'orange,mango',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ', ',\n      joinSeparator: ',',\n      end: false,\n      length: '2'\n    }\n  },\n  {\n    title: 'Truncate a list with custom separators',\n    description:\n      'This example shows how to truncate a list with custom separators.',\n    sampleText: 'apple | pineapple | lemon | orange | mango',\n    sampleResult: 'apple - pineapple - lemon',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ' | ',\n      joinSeparator: ' - ',\n      end: true,\n      length: '3'\n    }\n  }\n];\n\nexport default function Truncate({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: string) => {\n    if (input) {\n      try {\n        const length = parseInt(optionsValues.length, 10);\n        setResult(\n          truncateList(\n            optionsValues.splitOperatorType,\n            input,\n            optionsValues.splitSeparator,\n            optionsValues.joinSeparator,\n            optionsValues.end,\n            length\n          )\n        );\n      } catch (error) {\n        if (error instanceof Error) {\n          setResult(`Error: ${error.message}`);\n        } else {\n          setResult('An unknown error occurred');\n        }\n      }\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Split Options',\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('splitOperatorType', 'symbol')}\n            checked={values.splitOperatorType === 'symbol'}\n            title={'Split by Symbol'}\n          />\n          <SimpleRadio\n            onClick={() => updateField('splitOperatorType', 'regex')}\n            checked={values.splitOperatorType === 'regex'}\n            title={'Split by Regular Expression'}\n          />\n          <TextFieldWithDesc\n            value={values.splitSeparator}\n            onOwnChange={(val) => updateField('splitSeparator', val)}\n            description={'Separator to split the list'}\n          />\n          <TextFieldWithDesc\n            value={values.joinSeparator}\n            onOwnChange={(val) => updateField('joinSeparator', val)}\n            description={'Separator to join the truncated list'}\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Truncation Options',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.length}\n            onOwnChange={(val) => updateField('length', val)}\n            description={'Number of items to keep'}\n            type=\"number\"\n          />\n          <SimpleRadio\n            onClick={() => updateField('end', true)}\n            checked={values.end}\n            title={'Keep items from the beginning'}\n          />\n          <SimpleRadio\n            onClick={() => updateField('end', false)}\n            checked={!values.end}\n            title={'Keep items from the end'}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolTextInput title=\"Input List\" value={input} onChange={setInput} />\n      }\n      resultComponent={<ToolTextResult title=\"Truncated List\" value={result} />}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      validationSchema={validationSchema}\n      toolInfo={{\n        title: 'List Truncation',\n        description:\n          \"This tool allows you to truncate a list to a specific number of items. You can choose to keep items from the beginning or the end of the list, and specify custom separators for splitting and joining. It's useful for limiting the size of lists, creating previews, or extracting specific portions of data.\"\n      }}\n      exampleCards={exampleCards}\n      input={input}\n      setInput={setInput}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/list/truncate/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('list', {\n  path: 'truncate',\n  icon: 'material-symbols-light:content-cut',\n\n  keywords: ['truncate'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'list:truncate.title',\n    description: 'list:truncate.description',\n    shortDescription: 'list:truncate.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/list/truncate/service.ts",
    "content": "export type SplitOperatorType = 'symbol' | 'regex';\n\nexport function truncateList(\n  splitOperatorType: SplitOperatorType,\n  input: string,\n  splitSeparator: string,\n  joinSeparator: string,\n  end: boolean,\n  length?: number\n): string {\n  let array: string[];\n  let truncatedArray: string[];\n  switch (splitOperatorType) {\n    case 'symbol':\n      array = input.split(splitSeparator);\n      break;\n    case 'regex':\n      array = input.split(new RegExp(splitSeparator));\n      break;\n  }\n  if (length !== undefined) {\n    if (length < 0) {\n      throw new Error('Length value must be a positive number.');\n    }\n    truncatedArray = end\n      ? array.slice(0, length)\n      : array.slice(array.length - length, array.length);\n    return truncatedArray.join(joinSeparator);\n  }\n  throw new Error(\"Length value isn't a value number.\");\n}\n"
  },
  {
    "path": "src/pages/tools/list/truncate/truncate.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { SplitOperatorType, truncateList } from './service';\n\ndescribe('truncate function', () => {\n  it('should remove at the end (one element) if end is set to true', () => {\n    const input: string = 'apple, pineaple, lemon, orange';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const end = true;\n    const length = 3;\n\n    const result = truncateList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      end,\n      length\n    );\n\n    expect(result).toBe('apple pineaple lemon');\n  });\n\n  it('should return 3 elements from the start  if end is set to true', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const end = true;\n    const length = 3;\n\n    const result = truncateList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      end,\n      length\n    );\n\n    expect(result).toBe('apple pineaple lemon');\n  });\n\n  it('should return 3 elements from the start   if end is set to true', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const end = true;\n    const length = 3;\n\n    const result = truncateList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      end,\n      length\n    );\n\n    expect(result).toBe('apple pineaple lemon');\n  });\n\n  it('should return 3 elements from the end if end is set to true', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const end = false;\n    const length = 3;\n\n    const result = truncateList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      end,\n      length\n    );\n\n    expect(result).toBe('lemon orange mango');\n  });\n\n  it('should return a void string if length is set to 0', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const end = false;\n    const length = 0;\n\n    const result = truncateList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      end,\n      length\n    );\n\n    expect(result).toBe('');\n  });\n\n  it('should return an element (first) string if length is set to 1 and end is set to true', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const end = true;\n    const length = 1;\n\n    const result = truncateList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      end,\n      length\n    );\n\n    expect(result).toBe('apple');\n  });\n\n  it('should return an element (last) string if length is set to 1 and end is set to false', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const end = false;\n    const length = 1;\n\n    const result = truncateList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      end,\n      length\n    );\n\n    expect(result).toBe('mango');\n  });\n\n  it('should throw an error if the length value is negative', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const end = false;\n    const length = -5;\n\n    expect(() => {\n      truncateList(\n        splitOperatorType,\n        input,\n        splitSeparator,\n        joinSeparator,\n        end,\n        length\n      );\n    }).toThrow('Length value must be a positive number.');\n  });\n\n  it('should throw an error if the length value is left blank', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ' ';\n    const end = false;\n\n    expect(() => {\n      truncateList(\n        splitOperatorType,\n        input,\n        splitSeparator,\n        joinSeparator,\n        end\n      );\n    }).toThrow(\"Length value isn't a value number.\");\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/list/unwrap/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Box } from '@mui/material';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { SplitOperatorType, unwrapList } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport * as Yup from 'yup';\n\ninterface InitialValuesType {\n  splitOperatorType: SplitOperatorType;\n  splitSeparator: string;\n  joinSeparator: string;\n  deleteEmptyItems: boolean;\n  multiLevel: boolean;\n  trimItems: boolean;\n  left: string;\n  right: string;\n}\n\nconst initialValues: InitialValuesType = {\n  splitOperatorType: 'symbol',\n  splitSeparator: '\\n',\n  joinSeparator: '\\n',\n  deleteEmptyItems: true,\n  multiLevel: true,\n  trimItems: true,\n  left: '',\n  right: ''\n};\n\nconst validationSchema = Yup.object({\n  splitSeparator: Yup.string().required('The separator is required'),\n  joinSeparator: Yup.string().required('The join separator is required')\n});\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Unwrap quotes from list items',\n    description:\n      'This example shows how to remove quotes from each item in a list.',\n    sampleText: '\"apple\"\\n\"banana\"\\n\"orange\"',\n    sampleResult: 'apple\\nbanana\\norange',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: '\\n',\n      joinSeparator: '\\n',\n      deleteEmptyItems: true,\n      multiLevel: true,\n      trimItems: true,\n      left: '\"',\n      right: '\"'\n    }\n  },\n  {\n    title: 'Unwrap multiple levels of characters',\n    description:\n      'This example shows how to remove multiple levels of the same character from each item.',\n    sampleText: '###Hello###\\n##World##\\n#Test#',\n    sampleResult: 'Hello\\nWorld\\nTest',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: '\\n',\n      joinSeparator: '\\n',\n      deleteEmptyItems: true,\n      multiLevel: true,\n      trimItems: true,\n      left: '#',\n      right: '#'\n    }\n  },\n  {\n    title: 'Unwrap and join with custom separator',\n    description:\n      'This example shows how to unwrap items and join them with a custom separator.',\n    sampleText: '[item1]\\n[item2]\\n[item3]',\n    sampleResult: 'item1, item2, item3',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: '\\n',\n      joinSeparator: ', ',\n      deleteEmptyItems: true,\n      multiLevel: false,\n      trimItems: true,\n      left: '[',\n      right: ']'\n    }\n  }\n];\n\nexport default function Unwrap({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: string) => {\n    if (input) {\n      try {\n        setResult(\n          unwrapList(\n            optionsValues.splitOperatorType,\n            input,\n            optionsValues.splitSeparator,\n            optionsValues.joinSeparator,\n            optionsValues.deleteEmptyItems,\n            optionsValues.multiLevel,\n            optionsValues.trimItems,\n            optionsValues.left,\n            optionsValues.right\n          )\n        );\n      } catch (error) {\n        if (error instanceof Error) {\n          setResult(`Error: ${error.message}`);\n        } else {\n          setResult('An unknown error occurred');\n        }\n      }\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Split Options',\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('splitOperatorType', 'symbol')}\n            checked={values.splitOperatorType === 'symbol'}\n            title={'Split by Symbol'}\n          />\n          <SimpleRadio\n            onClick={() => updateField('splitOperatorType', 'regex')}\n            checked={values.splitOperatorType === 'regex'}\n            title={'Split by Regular Expression'}\n          />\n          <TextFieldWithDesc\n            value={values.splitSeparator}\n            onOwnChange={(val) => updateField('splitSeparator', val)}\n            description={'Separator to split the list'}\n          />\n          <TextFieldWithDesc\n            value={values.joinSeparator}\n            onOwnChange={(val) => updateField('joinSeparator', val)}\n            description={'Separator to join the unwrapped list'}\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Unwrap Options',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.left}\n            onOwnChange={(val) => updateField('left', val)}\n            description={'Characters to remove from the left side'}\n          />\n          <TextFieldWithDesc\n            value={values.right}\n            onOwnChange={(val) => updateField('right', val)}\n            description={'Characters to remove from the right side'}\n          />\n          <CheckboxWithDesc\n            checked={values.multiLevel}\n            onChange={(checked) => updateField('multiLevel', checked)}\n            title={'Remove multiple levels of wrapping'}\n          />\n          <CheckboxWithDesc\n            checked={values.trimItems}\n            onChange={(checked) => updateField('trimItems', checked)}\n            title={'Trim whitespace from items'}\n          />\n          <CheckboxWithDesc\n            checked={values.deleteEmptyItems}\n            onChange={(checked) => updateField('deleteEmptyItems', checked)}\n            title={'Remove empty items'}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolTextInput title=\"Input List\" value={input} onChange={setInput} />\n      }\n      resultComponent={<ToolTextResult title=\"Unwrapped List\" value={result} />}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      validationSchema={validationSchema}\n      toolInfo={{\n        title: 'List Unwrapping',\n        description:\n          \"This tool allows you to remove wrapping characters from each item in a list. You can specify characters to remove from the left and right sides, handle multiple levels of wrapping, and control how the list is processed. It's useful for cleaning up data, removing quotes or brackets, and formatting lists.\"\n      }}\n      exampleCards={exampleCards}\n      input={input}\n      setInput={setInput}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/list/unwrap/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('list', {\n  path: 'unwrap',\n  icon: 'material-symbols-light:unfold-more',\n\n  keywords: ['unwrap'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'list:unwrap.title',\n    description: 'list:unwrap.description',\n    shortDescription: 'list:unwrap.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/list/unwrap/service.ts",
    "content": "export type SplitOperatorType = 'symbol' | 'regex';\n\nfunction leftUnwrap(\n  row: string,\n  left: string = '',\n  multiLevel: boolean\n): string {\n  if (left === '') return row; // Prevent infinite loop if left is an empty string\n  while (row.startsWith(left)) {\n    row = row.slice(left.length);\n    if (!multiLevel) {\n      break;\n    }\n  }\n  return row;\n}\n\nfunction rightUnwrap(\n  row: string,\n  right: string = '',\n  multiLevel: boolean\n): string {\n  if (right === '') return row; // Prevent infinite loop if right is an empty string\n  while (row.endsWith(right)) {\n    row = row.slice(0, row.length - right.length);\n    if (!multiLevel) {\n      break;\n    }\n  }\n  return row;\n}\n\nexport function unwrapList(\n  splitOperatorType: SplitOperatorType,\n  input: string,\n  splitSeparator: string,\n  joinSeparator: string,\n  deleteEmptyItems: boolean,\n  multiLevel: boolean,\n  trimItems: boolean,\n  left: string = '',\n  right: string = ''\n): string {\n  let array: string[];\n  let unwrappedArray: string[] = [];\n  switch (splitOperatorType) {\n    case 'symbol':\n      array = input.split(splitSeparator);\n      break;\n    case 'regex':\n      array = input.split(new RegExp(splitSeparator));\n      break;\n  }\n  if (deleteEmptyItems) {\n    array = array.filter(Boolean);\n  }\n\n  // for each element of array unwrap left side then right side and push the result to a final array\n  for (let row of array) {\n    row = leftUnwrap(row, left, multiLevel);\n    row = rightUnwrap(row, right, multiLevel);\n    unwrappedArray.push(row);\n  }\n  // trim items if needed\n  if (trimItems) {\n    unwrappedArray = unwrappedArray.map((item) => item.trim());\n  }\n  return unwrappedArray.join(joinSeparator);\n}\n"
  },
  {
    "path": "src/pages/tools/list/unwrap/unwrap.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { unwrapList } from './service';\n\ndescribe('unwrapList function', () => {\n  it('should unwrap elements correctly with symbol split', () => {\n    const input = '##Hello##\\n##World##';\n    const result = unwrapList(\n      'symbol',\n      input,\n      '\\n',\n      ' ',\n      true,\n      true,\n      true,\n      '#',\n      '#'\n    );\n    expect(result).toBe('Hello World');\n  });\n\n  it('should unwrap elements correctly with regex split', () => {\n    const input = '##Hello##||##World##';\n    const result = unwrapList(\n      'regex',\n      input,\n      '\\\\|\\\\|',\n      ' ',\n      true,\n      true,\n      true,\n      '#',\n      '#'\n    );\n    expect(result).toBe('Hello World');\n  });\n\n  it('should handle multiple levels of unwrapping', () => {\n    const input = '###Hello###';\n    const result = unwrapList(\n      'symbol',\n      input,\n      '\\n',\n      ' ',\n      true,\n      true,\n      true,\n      '#',\n      '#'\n    );\n    expect(result).toBe('Hello');\n  });\n\n  it('should handle single level of unwrapping', () => {\n    const input = '###Hello###';\n    const result = unwrapList(\n      'symbol',\n      input,\n      '\\n',\n      ' ',\n      true,\n      false,\n      true,\n      '#',\n      '#'\n    );\n    expect(result).toBe('##Hello##');\n  });\n\n  it('should delete empty items', () => {\n    const input = '##Hello##\\n\\n##World##';\n    const result = unwrapList(\n      'symbol',\n      input,\n      '\\n',\n      ' ',\n      true,\n      true,\n      true,\n      '#',\n      '#'\n    );\n    expect(result).toBe('Hello World');\n  });\n\n  it('should keep empty items if deleteEmptyItems is false', () => {\n    const input = '##Hello##\\n\\n##World##';\n    const result = unwrapList(\n      'symbol',\n      input,\n      '\\n',\n      ' ',\n      false,\n      true,\n      true,\n      '#',\n      '#'\n    );\n    expect(result).toBe('Hello  World');\n  });\n\n  it('should trim items', () => {\n    const input = '##  Hello  ##\\n##  World  ##';\n    const result = unwrapList(\n      'symbol',\n      input,\n      '\\n',\n      ' ',\n      true,\n      true,\n      true,\n      '#',\n      '#'\n    );\n    expect(result).toBe('Hello World');\n  });\n\n  it('should handle no left or right unwrapping', () => {\n    const input = 'Hello\\nWorld';\n    const result = unwrapList('symbol', input, '\\n', ' ', true, true, true);\n    expect(result).toBe('Hello World');\n  });\n\n  it('should handle mixed levels of unwrapping', () => {\n    const input = '###Hello##\\n#World###';\n    const result = unwrapList(\n      'symbol',\n      input,\n      '\\n',\n      ' ',\n      true,\n      true,\n      true,\n      '#',\n      '#'\n    );\n    expect(result).toBe('Hello World');\n  });\n\n  it('should handle complex regex split', () => {\n    const input = '##Hello##||###World###||####Test####';\n    const result = unwrapList(\n      'regex',\n      input,\n      '\\\\|\\\\|',\n      ' ',\n      true,\n      true,\n      true,\n      '#',\n      '#'\n    );\n    expect(result).toBe('Hello World Test');\n  });\n\n  it('should handle different joinSeparator', () => {\n    const input = '##Hello##\\n##World##';\n    const result = unwrapList(\n      'symbol',\n      input,\n      '\\n',\n      '-',\n      true,\n      true,\n      true,\n      '#',\n      '#'\n    );\n    expect(result).toBe('Hello-World');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/list/wrap/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Box } from '@mui/material';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { SplitOperatorType, wrapList } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport * as Yup from 'yup';\nimport { useTranslation } from 'react-i18next';\n\ninterface InitialValuesType {\n  splitOperatorType: SplitOperatorType;\n  splitSeparator: string;\n  joinSeparator: string;\n  deleteEmptyItems: boolean;\n  left: string;\n  right: string;\n}\n\nconst initialValues: InitialValuesType = {\n  splitOperatorType: 'symbol',\n  splitSeparator: ',',\n  joinSeparator: ',',\n  deleteEmptyItems: true,\n  left: '\"',\n  right: '\"'\n};\n\nconst validationSchema = Yup.object({\n  splitSeparator: Yup.string().required('The separator is required'),\n  joinSeparator: Yup.string().required('The join separator is required')\n});\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Wrap list items with quotes',\n    description:\n      'This example shows how to wrap each item in a list with quotes.',\n    sampleText: 'apple,banana,orange',\n    sampleResult: '\"apple\",\"banana\",\"orange\"',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ',',\n      joinSeparator: ',',\n      deleteEmptyItems: true,\n      left: '\"',\n      right: '\"'\n    }\n  },\n  {\n    title: 'Wrap list items with brackets',\n    description:\n      'This example shows how to wrap each item in a list with brackets.',\n    sampleText: 'item1,item2,item3',\n    sampleResult: '[item1],[item2],[item3]',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ',',\n      joinSeparator: ',',\n      deleteEmptyItems: true,\n      left: '[',\n      right: ']'\n    }\n  },\n  {\n    title: 'Wrap list items with custom text',\n    description:\n      'This example shows how to wrap each item with different text on each side.',\n    sampleText: 'apple,banana,orange',\n    sampleResult:\n      'prefix-apple-suffix,prefix-banana-suffix,prefix-orange-suffix',\n    sampleOptions: {\n      splitOperatorType: 'symbol',\n      splitSeparator: ',',\n      joinSeparator: ',',\n      deleteEmptyItems: true,\n      left: 'prefix-',\n      right: '-suffix'\n    }\n  }\n];\n\nexport default function Wrap({ title }: ToolComponentProps) {\n  const { t } = useTranslation('list');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: string) => {\n    if (input) {\n      try {\n        setResult(\n          wrapList(\n            optionsValues.splitOperatorType,\n            input,\n            optionsValues.splitSeparator,\n            optionsValues.joinSeparator,\n            optionsValues.deleteEmptyItems,\n            optionsValues.left,\n            optionsValues.right\n          )\n        );\n      } catch (error) {\n        if (error instanceof Error) {\n          setResult(`Error: ${error.message}`);\n        } else {\n          setResult('An unknown error occurred');\n        }\n      }\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('wrap.splitOptions'),\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('splitOperatorType', 'symbol')}\n            checked={values.splitOperatorType === 'symbol'}\n            title={t('wrap.splitBySymbol')}\n          />\n          <SimpleRadio\n            onClick={() => updateField('splitOperatorType', 'regex')}\n            checked={values.splitOperatorType === 'regex'}\n            title={t('wrap.splitByRegex')}\n          />\n          <TextFieldWithDesc\n            value={values.splitSeparator}\n            onOwnChange={(val) => updateField('splitSeparator', val)}\n            description={t('wrap.splitSeparatorDescription')}\n          />\n          <TextFieldWithDesc\n            value={values.joinSeparator}\n            onOwnChange={(val) => updateField('joinSeparator', val)}\n            description={t('wrap.joinSeparatorDescription')}\n          />\n          <CheckboxWithDesc\n            checked={values.deleteEmptyItems}\n            onChange={(checked) => updateField('deleteEmptyItems', checked)}\n            title={t('wrap.removeEmptyItems')}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('wrap.wrapOptions'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.left}\n            onOwnChange={(val) => updateField('left', val)}\n            description={t('wrap.leftTextDescription')}\n          />\n          <TextFieldWithDesc\n            value={values.right}\n            onOwnChange={(val) => updateField('right', val)}\n            description={t('wrap.rightTextDescription')}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolTextInput\n          title={t('wrap.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('wrap.resultTitle')} value={result} />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      validationSchema={validationSchema}\n      toolInfo={{\n        title: t('wrap.toolInfo.title'),\n        description: t('wrap.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n      input={input}\n      setInput={setInput}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/list/wrap/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('list', {\n  path: 'wrap',\n  icon: 'material-symbols-light:wrap-text',\n\n  keywords: ['wrap'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'list:wrap.title',\n    description: 'list:wrap.description',\n    shortDescription: 'list:wrap.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/list/wrap/service.ts",
    "content": "export type SplitOperatorType = 'symbol' | 'regex';\n\nfunction wrap(array: string[], left: string, right: string): string[] {\n  return array.map((element) => left + element + right);\n}\n\nexport function wrapList(\n  splitOperatorType: SplitOperatorType,\n  input: string,\n  splitSeparator: string,\n  joinSeparator: string,\n  deleteEmptyItems: boolean,\n  left: string = '',\n  right: string = ''\n): string {\n  let array: string[];\n  let wrappedArray: string[];\n  switch (splitOperatorType) {\n    case 'symbol':\n      array = input.split(splitSeparator);\n      break;\n    case 'regex':\n      array = input.split(new RegExp(splitSeparator));\n      break;\n  }\n  if (deleteEmptyItems) {\n    array = array.filter(Boolean);\n  }\n  wrappedArray = wrap(array, left, right);\n  return wrappedArray.join(joinSeparator);\n}\n"
  },
  {
    "path": "src/pages/tools/list/wrap/wrap.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { SplitOperatorType, wrapList } from './service';\n\ndescribe('wrap function', () => {\n  it('should return the same input if no left and right are blanked', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ', ';\n    const deleteEmptyItems = false;\n\n    const result = wrapList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      deleteEmptyItems\n    );\n\n    expect(result).toBe('apple, pineaple, lemon, orange, mango');\n  });\n\n  it('should append to left if defined', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ', ';\n    const left = 'the ';\n    const deleteEmptyItems = false;\n\n    const result = wrapList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      deleteEmptyItems,\n      left\n    );\n\n    expect(result).toBe(\n      'the apple, the pineaple, the lemon, the orange, the mango'\n    );\n  });\n\n  it('should append to right if defined', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ', ';\n    const left = '';\n    const right = 'z';\n    const deleteEmptyItems = false;\n\n    const result = wrapList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      deleteEmptyItems,\n      left,\n      right\n    );\n\n    expect(result).toBe('applez, pineaplez, lemonz, orangez, mangoz');\n  });\n\n  it('should append to both side if both defined', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ', ';\n    const deleteEmptyItems = false;\n    const left = 'K';\n    const right = 'z';\n\n    const result = wrapList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      deleteEmptyItems,\n      left,\n      right\n    );\n\n    expect(result).toBe('Kapplez, Kpineaplez, Klemonz, Korangez, Kmangoz');\n  });\n\n  it('should append to both side if both defined and not delete empty items', () => {\n    const input: string = 'apple, pineaple, lemon, orange, mango, ';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ', ';\n    const deleteEmptyItems = false;\n    const left = 'K';\n    const right = 'z';\n\n    const result = wrapList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      deleteEmptyItems,\n      left,\n      right\n    );\n\n    expect(result).toBe('Kapplez, Kpineaplez, Klemonz, Korangez, Kmangoz, Kz');\n  });\n\n  it('should append to both side if both defined and delete empty items', () => {\n    const input: string = 'apple, pineaple, lemon, , orange, mango';\n    const splitOperatorType: SplitOperatorType = 'symbol';\n    const splitSeparator = ', ';\n    const joinSeparator = ', ';\n    const deleteEmptyItems = true;\n    const left = 'K';\n    const right = 'z';\n\n    const result = wrapList(\n      splitOperatorType,\n      input,\n      splitSeparator,\n      joinSeparator,\n      deleteEmptyItems,\n      left,\n      right\n    );\n\n    expect(result).toBe('Kapplez, Kpineaplez, Klemonz, Korangez, Kmangoz');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/number/arithmetic-sequence/arithmetic-sequence.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { generateArithmeticSequence } from './service';\n\ndescribe('generateArithmeticSequence', () => {\n  it('should generate basic arithmetic sequence', () => {\n    const result = generateArithmeticSequence(1, 2, 5, ', ');\n    expect(result).toBe('1, 3, 5, 7, 9');\n  });\n\n  it('should handle negative first term', () => {\n    const result = generateArithmeticSequence(-5, 2, 5, ' ');\n    expect(result).toBe('-5 -3 -1 1 3');\n  });\n\n  it('should handle negative common difference', () => {\n    const result = generateArithmeticSequence(10, -2, 5, ',');\n    expect(result).toBe('10,8,6,4,2');\n  });\n\n  it('should handle decimal numbers', () => {\n    const result = generateArithmeticSequence(1.5, 0.5, 4, ' ');\n    expect(result).toBe('1.5 2 2.5 3');\n  });\n\n  it('should handle single term sequence', () => {\n    const result = generateArithmeticSequence(1, 2, 1, ',');\n    expect(result).toBe('1');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/number/arithmetic-sequence/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { generateArithmeticSequence } from './service';\nimport * as Yup from 'yup';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\n\ntype InitialValuesType = {\n  firstTerm: string;\n  commonDifference: string;\n  numberOfTerms: string;\n  separator: string;\n};\n\nconst initialValues: InitialValuesType = {\n  firstTerm: '1',\n  commonDifference: '2',\n  numberOfTerms: '10',\n  separator: ', '\n};\n\nconst validationSchema = Yup.object({\n  firstTerm: Yup.number().required('First term is required'),\n  commonDifference: Yup.number().required('Common difference is required'),\n  numberOfTerms: Yup.number()\n    .min(1, 'Must generate at least 1 term')\n    .max(1000, 'Maximum 1000 terms allowed')\n    .required('Number of terms is required'),\n  separator: Yup.string().required('Separator is required')\n});\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Basic Arithmetic Sequence',\n    description:\n      'Generate a sequence starting at 1, increasing by 2, for 5 terms',\n    sampleOptions: {\n      firstTerm: '1',\n      commonDifference: '2',\n      numberOfTerms: '5',\n      separator: ', '\n    },\n    sampleResult: '1, 3, 5, 7, 9'\n  },\n  {\n    title: 'Negative Sequence',\n    description: 'Generate a decreasing sequence starting at 10',\n    sampleOptions: {\n      firstTerm: '10',\n      commonDifference: '-3',\n      numberOfTerms: '4',\n      separator: ' → '\n    },\n    sampleResult: '10 → 7 → 4 → 1'\n  },\n  {\n    title: 'Decimal Sequence',\n    description: 'Generate a sequence with decimal numbers',\n    sampleOptions: {\n      firstTerm: '0.5',\n      commonDifference: '0.5',\n      numberOfTerms: '6',\n      separator: ' '\n    },\n    sampleResult: '0.5 1 1.5 2 2.5 3'\n  }\n];\n\nexport default function ArithmeticSequence({ title }: ToolComponentProps) {\n  const { t } = useTranslation('number');\n  const [result, setResult] = useState<string>('');\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={null}\n      resultComponent={\n        <ToolTextResult\n          title={t('arithmeticSequence.resultTitle')}\n          value={result}\n        />\n      }\n      initialValues={initialValues}\n      validationSchema={validationSchema}\n      exampleCards={exampleCards}\n      toolInfo={{\n        title: t('arithmeticSequence.toolInfo.title'),\n        description: t('arithmeticSequence.toolInfo.description')\n      }}\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('arithmeticSequence.sequenceParameters'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                description={t('arithmeticSequence.firstTermDescription')}\n                value={values.firstTerm}\n                onOwnChange={(val) => updateField('firstTerm', val)}\n                type=\"number\"\n              />\n              <TextFieldWithDesc\n                description={t(\n                  'arithmeticSequence.commonDifferenceDescription'\n                )}\n                value={values.commonDifference}\n                onOwnChange={(val) => updateField('commonDifference', val)}\n                type=\"number\"\n              />\n              <TextFieldWithDesc\n                description={t('arithmeticSequence.numberOfTermsDescription')}\n                value={values.numberOfTerms}\n                onOwnChange={(val) => updateField('numberOfTerms', val)}\n                type=\"number\"\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('arithmeticSequence.outputFormat'),\n          component: (\n            <TextFieldWithDesc\n              description={t('arithmeticSequence.separatorDescription')}\n              value={values.separator}\n              onOwnChange={(val) => updateField('separator', val)}\n            />\n          )\n        }\n      ]}\n      compute={(values) => {\n        const sequence = generateArithmeticSequence(\n          Number(values.firstTerm),\n          Number(values.commonDifference),\n          Number(values.numberOfTerms),\n          values.separator\n        );\n        setResult(sequence);\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/number/arithmetic-sequence/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('number', {\n  path: 'arithmetic-sequence',\n  icon: 'material-symbols:functions',\n\n  keywords: ['arithmetic', 'sequence', 'math', 'progression'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'number:arithmeticSequence.title',\n    description: 'number:arithmeticSequence.description',\n    shortDescription: 'number:arithmeticSequence.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/number/arithmetic-sequence/service.ts",
    "content": "export function generateArithmeticSequence(\n  firstTerm: number,\n  commonDifference: number,\n  numberOfTerms: number,\n  separator: string\n): string {\n  const sequence: number[] = [];\n  for (let i = 0; i < numberOfTerms; i++) {\n    const term = firstTerm + i * commonDifference;\n    sequence.push(term);\n  }\n  return sequence.join(separator);\n}\n"
  },
  {
    "path": "src/pages/tools/number/byte-converter/byte-converter.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { byteConverter } from './service';\nimport { InitialValuesType, DataUnit } from './types';\n\ndescribe('byteConverter', () => {\n  const makeOptions = (\n    fromUnit: DataUnit,\n    toUnit: DataUnit,\n    precision: number\n  ): InitialValuesType => ({\n    fromUnit,\n    toUnit,\n    precision\n  });\n\n  it('converts bits to bytes', () => {\n    expect(byteConverter('8', makeOptions('b', 'B', 2))).toBe('1');\n    expect(byteConverter('1', makeOptions('b', 'B', 4))).toBe('0.125');\n  });\n\n  it('converts bytes to bits', () => {\n    expect(byteConverter('1', makeOptions('B', 'b', 0))).toBe('8');\n  });\n\n  it('converts decimal units correctly', () => {\n    expect(byteConverter('1', makeOptions('KB', 'B', 0))).toBe('1000');\n    expect(byteConverter('1', makeOptions('MB', 'KB', 0))).toBe('1000');\n    expect(byteConverter('1', makeOptions('GB', 'MB', 0))).toBe('1000');\n  });\n\n  it('converts binary units correctly', () => {\n    expect(byteConverter('1', makeOptions('KiB', 'B', 0))).toBe('1024');\n    expect(byteConverter('1', makeOptions('MiB', 'KiB', 0))).toBe('1024');\n    expect(byteConverter('1', makeOptions('GiB', 'MiB', 0))).toBe('1024');\n  });\n\n  it('converts decimal to binary units correctly', () => {\n    expect(byteConverter('1', makeOptions('GB', 'MiB', 2))).toBe('953.67');\n    expect(byteConverter('1', makeOptions('TB', 'GiB', 2))).toBe('931.32');\n  });\n\n  it('converts binary to decimal units correctly', () => {\n    expect(byteConverter('1', makeOptions('GiB', 'MB', 2))).toBe('1073.74');\n    expect(byteConverter('1', makeOptions('TiB', 'GB', 2))).toBe('1099.51');\n  });\n\n  it('respects precision rounding', () => {\n    expect(byteConverter('1', makeOptions('GB', 'MiB', 0))).toBe('954');\n    expect(byteConverter('1', makeOptions('GB', 'MiB', 1))).toBe('953.7');\n    expect(byteConverter('1', makeOptions('GB', 'MiB', 3))).toBe('953.674');\n  });\n\n  it('handles zero correctly', () => {\n    expect(byteConverter('0', makeOptions('GB', 'MB', 2))).toBe('0');\n  });\n\n  it('handles identity conversions', () => {\n    expect(byteConverter('123', makeOptions('MB', 'MB', 2))).toBe('123');\n    expect(byteConverter('1', makeOptions('GiB', 'GiB', 5))).toBe('1');\n  });\n\n  it('handles large values', () => {\n    expect(byteConverter('1024', makeOptions('GiB', 'TiB', 4))).toBe('1');\n  });\n\n  it('handles multiline input', () => {\n    const input = '1\\n2\\n3';\n    const result = byteConverter(input, makeOptions('GB', 'MB', 0));\n    expect(result).toBe('1000\\n2000\\n3000');\n  });\n\n  it('ignores empty lines', () => {\n    const input = '1\\n\\n3';\n    const result = byteConverter(input, makeOptions('GB', 'MB', 0));\n    expect(result).toBe('1000\\n\\n3000');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/number/byte-converter/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { byteConverter } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\nimport { InitialValuesType, DATA_UNITS } from './types';\n\nconst initialValues: InitialValuesType = {\n  fromUnit: 'GB',\n  toUnit: 'KB',\n  precision: 2\n};\n\nexport const exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Convert Gigabytes to Megabytes',\n    description:\n      'This example converts a list of storage values in Gigabytes (GB) into Megabytes (MB) using the SI system. Each line represents a different storage size. The output shows the same values converted to MB with the specified precision.',\n    sampleText: `1\n2.5\n10\n0.75\n50`,\n    sampleResult: `1000\n2500\n10000\n750\n50000`,\n    sampleOptions: {\n      fromUnit: 'GB',\n      toUnit: 'MB',\n      precision: 0\n    }\n  },\n  {\n    title: 'Convert MiB to GiB (IEC System)',\n    description:\n      'This example converts values in Mebibytes (MiB) to Gibibytes (GiB) using the IEC binary system. Each line of input represents a different memory size, and the output shows the conversion rounded to 2 decimal places.',\n    sampleText: `1024\n2048\n5120\n256\n8192`,\n    sampleResult: `1\n2\n5\n0.25\n8`,\n    sampleOptions: {\n      fromUnit: 'MiB',\n      toUnit: 'GiB',\n      precision: 2\n    }\n  },\n  {\n    title: 'Cross-System Conversion: GB to GiB',\n    description:\n      'This example demonstrates a cross-system conversion from SI units (GB) to IEC units (GiB). Since GB and GiB are slightly different, the output shows fractional values with high precision. This is useful when comparing storage reported by operating systems vs manufacturers.',\n    sampleText: `1\n5\n10\n50\n100`,\n    sampleResult: `0.931\n4.655\n9.313\n46.566\n93.132`,\n    sampleOptions: {\n      fromUnit: 'GB',\n      toUnit: 'GiB',\n      precision: 3\n    }\n  },\n  {\n    title: 'Convert Bytes to Bits',\n    description:\n      'This example converts values from Bytes (B) to Bits (b). Since 1 Byte = 8 Bits, each input value is multiplied by 8. The precision is set to 0 because the result is always an integer.',\n    sampleText: `1\n5\n10\n50\n100`,\n    sampleResult: `8\n40\n80\n400\n800`,\n    sampleOptions: {\n      fromUnit: 'B',\n      toUnit: 'b',\n      precision: 0\n    }\n  }\n];\n\nexport default function ByteConverter({ title }: ToolComponentProps) {\n  const { t } = useTranslation('number');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: any) => {\n    setResult(byteConverter(input, optionsValues));\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      exampleCards={exampleCards}\n      initialValues={initialValues}\n      inputComponent={\n        <ToolTextInput\n          title={t('byteConverter.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('byteConverter.outputTitle')} value={result} />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('byteConverter.unit.title'),\n          component: (\n            <Box>\n              <SelectWithDesc\n                selected={values.fromUnit}\n                options={DATA_UNITS.map((unit) => ({\n                  label: unit,\n                  value: unit\n                }))}\n                onChange={(value) => updateField('fromUnit', value)}\n                description={t('byteConverter.unit.from')}\n              />\n\n              <SelectWithDesc\n                selected={values.toUnit}\n                options={DATA_UNITS.map((unit) => ({\n                  label: unit,\n                  value: unit\n                }))}\n                onChange={(value) => updateField('toUnit', value)}\n                description={t('byteConverter.unit.to')}\n              />\n\n              <TextFieldWithDesc\n                description={t('byteConverter.unit.precision')}\n                value={values.precision}\n                onOwnChange={(val) => updateField('precision', Number(val))}\n                type={'number'}\n              />\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/number/byte-converter/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('number', {\n  path: 'byte-converter',\n  icon: 'fluent:calculator-arrow-clockwise-24-regular',\n\n  keywords: ['byte', 'converter'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'number:byteConverter.title',\n    description: 'number:byteConverter.description',\n    shortDescription: 'number:byteConverter.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/number/byte-converter/service.ts",
    "content": "import { DataUnit, InitialValuesType, UNIT_MAP } from './types';\n\nfunction compute(\n  value: number,\n  fromUnit: DataUnit,\n  toUnit: DataUnit,\n  precision: number\n): number {\n  if (precision < 0 || value < 0) return 0;\n  const bytes = value * UNIT_MAP[fromUnit];\n  const result = bytes / UNIT_MAP[toUnit];\n  return Number(result.toFixed(precision));\n}\n\nexport function byteConverter(input: string, options: InitialValuesType) {\n  if (!input) return '';\n\n  const values = input.split('\\n');\n  const result: string[] = [];\n\n  const { fromUnit, toUnit, precision } = options;\n\n  for (const elem of values) {\n    const trimmed = elem.trim();\n\n    if (!trimmed) {\n      result.push('');\n      continue;\n    }\n\n    const computed = compute(Number(trimmed), fromUnit, toUnit, precision);\n    result.push(String(computed));\n  }\n\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/number/byte-converter/types.ts",
    "content": "type SIUnit = 'b' | 'B' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB' | 'EB';\ntype IECUnit = 'b' | 'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB' | 'PiB' | 'EiB';\n\nexport type DataUnit = SIUnit | IECUnit;\n\nconst SI_UNITS: SIUnit[] = ['b', 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];\nconst IEC_UNITS: IECUnit[] = [\n  'b',\n  'B',\n  'KiB',\n  'MiB',\n  'GiB',\n  'TiB',\n  'PiB',\n  'EiB'\n];\n\nexport const DATA_UNITS: DataUnit[] = [\n  ...SI_UNITS,\n  ...IEC_UNITS.filter((unit) => !SI_UNITS.includes(unit as SIUnit))\n];\n\nexport const UNIT_MAP: Record<DataUnit, number> = {\n  b: 1 / 8,\n  B: 1,\n  KB: 1e3,\n  MB: 1e6,\n  GB: 1e9,\n  TB: 1e12,\n  PB: 1e15,\n  EB: 1e18,\n  KiB: 1024,\n  MiB: 1024 ** 2,\n  GiB: 1024 ** 3,\n  TiB: 1024 ** 4,\n  PiB: 1024 ** 5,\n  EiB: 1024 ** 6\n};\n\nexport type InitialValuesType = {\n  fromUnit: DataUnit;\n  toUnit: DataUnit;\n  precision: number;\n};\n"
  },
  {
    "path": "src/pages/tools/number/generate/generate.service.test.ts",
    "content": "// Import necessary modules and functions\nimport { describe, expect, it } from 'vitest';\nimport { listOfIntegers } from './service';\n\n// Define test cases for the listOfIntegers function\ndescribe('listOfIntegers function', () => {\n  it('should generate a list of integers with comma separator', () => {\n    const initialValue = 1;\n    const step = 2;\n    const count = 5;\n    const separator = ', ';\n\n    const result = listOfIntegers(initialValue, count, step, separator);\n    expect(result).toBe('1, 3, 5, 7, 9');\n  });\n\n  it('should generate a list of integers with dash separator', () => {\n    const initialValue = 0;\n    const step = 3;\n    const count = 4;\n    const separator = ' - ';\n\n    const result = listOfIntegers(initialValue, count, step, separator);\n    expect(result).toBe('0 - 3 - 6 - 9');\n  });\n\n  it('should handle negative initial value and step', () => {\n    const initialValue = -10;\n    const step = -2;\n    const count = 5;\n    const separator = ' ';\n\n    const result = listOfIntegers(initialValue, count, step, separator);\n    expect(result).toBe('-10 -12 -14 -16 -18');\n  });\n\n  it('should handle negative initial value and positive step', () => {\n    const initialValue = -10;\n    const step = 2;\n    const count = 5;\n    const separator = ' ';\n\n    const result = listOfIntegers(initialValue, count, step, separator);\n    expect(result).toBe('-10 -8 -6 -4 -2');\n  });\n\n  it('should float value', () => {\n    const initialValue = -10;\n    const step = 2.5;\n    const count = 5;\n    const separator = ' ';\n\n    const result = listOfIntegers(initialValue, count, step, separator);\n    expect(result).toBe('-10 -7.5 -5 -2.5 0');\n  });\n\n  it('should generate a constant sequence if the step is 0', () => {\n    const initialValue = 1;\n    const step = 0;\n    const count = 5;\n    const separator = ' ';\n\n    const result = listOfIntegers(initialValue, count, step, separator);\n    expect(result).toBe('1 1 1 1 1');\n  });\n\n  it('should generate a constant sequence if the step is 0', () => {\n    const initialValue = 1;\n    const step = 0;\n    const count = 5;\n    const separator = ' ';\n\n    const result = listOfIntegers(initialValue, count, step, separator);\n    expect(result).toBe('1 1 1 1 1');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/number/generate/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { listOfIntegers } from './service';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  firstValue: '1',\n  numberOfNumbers: '10',\n  step: '1',\n  separator: '\\\\n'\n};\n\nexport default function GenerateNumbers({ title }: ToolComponentProps) {\n  const { t } = useTranslation('number');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: typeof initialValues) => {\n    const { firstValue, numberOfNumbers, separator, step } = optionsValues;\n    setResult(\n      listOfIntegers(\n        Number(firstValue),\n        Number(numberOfNumbers),\n        Number(step),\n        separator\n      )\n    );\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('generate.arithmeticSequenceOption'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                description={t('generate.startSequenceDescription')}\n                value={values.firstValue}\n                onOwnChange={(val) => updateField('firstValue', val)}\n                type={'number'}\n              />\n              <TextFieldWithDesc\n                description={t('generate.stepDescription')}\n                value={values.step}\n                onOwnChange={(val) => updateField('step', val)}\n                type={'number'}\n              />\n              <TextFieldWithDesc\n                description={t('generate.numberOfElementsDescription')}\n                value={values.numberOfNumbers}\n                onOwnChange={(val) => updateField('numberOfNumbers', val)}\n                type={'number'}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('generate.separator'),\n          component: (\n            <TextFieldWithDesc\n              description={t('generate.separatorDescription')}\n              value={values.separator}\n              onOwnChange={(val) => updateField('separator', val)}\n            />\n          )\n        }\n      ]}\n      compute={compute}\n      resultComponent={\n        <ToolTextResult title={t('generate.resultTitle')} value={result} />\n      }\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/number/generate/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('number', {\n  path: 'generate',\n  icon: 'material-symbols:add-circle',\n\n  keywords: ['generate', 'random', 'numbers'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'number:generate.title',\n    description: 'number:generate.description',\n    shortDescription: 'number:generate.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/number/generate/service.ts",
    "content": "export function listOfIntegers(\n  first_value: number,\n  number_of_numbers: number,\n  step: number,\n  separator: string\n) {\n  const result: number[] = [];\n  for (let i: number = 0; i < number_of_numbers; i++) {\n    const value: number = first_value + i * step;\n    result.push(value);\n  }\n  return result.join(separator);\n}\n"
  },
  {
    "path": "src/pages/tools/number/generic-calc/data/index.ts",
    "content": "import ohmslaw from './ohmsLaw';\nimport voltageDropInWire from './voltageDropInWire';\nimport sphereArea from './sphereArea';\nimport sphereVolume from './sphereVolume';\nimport slackline from './slackline';\n\nexport default [\n  ohmslaw,\n  voltageDropInWire,\n  sphereArea,\n  sphereVolume,\n  slackline\n];\n"
  },
  {
    "path": "src/pages/tools/number/generic-calc/data/ohmsLaw.ts",
    "content": "import type { GenericCalcType } from './types';\n\nconst ohmsLawCalc: GenericCalcType = {\n  icon: 'mdi:ohm',\n  keywords: [\n    'ohm',\n    'voltage',\n    'current',\n    'resistance',\n    'electrical',\n    'circuit',\n    'electronics',\n    'power',\n    'V=IR'\n  ],\n  path: 'ohms-law',\n  formula: 'V = I * R',\n  i18n: {\n    name: 'number:ohmsLaw.title',\n    description: 'number:ohmsLaw.description',\n    shortDescription: 'number:ohmsLaw.shortDescription',\n    longDescription: 'number:ohmsLaw.longDescription'\n  },\n  presets: [],\n  variables: [\n    {\n      name: 'V',\n      title: 'Voltage',\n      unit: 'volt',\n      default: 5\n    },\n    {\n      name: 'I',\n      title: 'Current',\n      unit: 'ampere',\n      default: 1\n    },\n    {\n      name: 'R',\n      title: 'Resistance',\n      unit: 'ohm'\n    }\n  ]\n};\n\nexport default ohmsLawCalc;\n"
  },
  {
    "path": "src/pages/tools/number/generic-calc/data/slackline.ts",
    "content": "import type { GenericCalcType } from './types';\n\nconst slackline: GenericCalcType = {\n  icon: 'mdi:bridge',\n  keywords: [\n    'mechanical',\n    'rope',\n    'webbing',\n    'cord',\n    'string',\n    'tension',\n    'clothesline'\n  ],\n  path: 'slackline-tension',\n  formula: 'T = (W * sqrt((S**2) + ((L/2)**2)) )/ (2S)',\n  presets: [],\n  i18n: {\n    name: 'number:slackline.title',\n    description: 'number:slackline.description',\n    shortDescription: 'number:slackline.shortDescription',\n    longDescription: 'number:slackline.longDescription'\n  },\n  variables: [\n    {\n      name: 'L',\n      title: 'Length',\n      unit: 'meter',\n      default: 2\n    },\n    {\n      name: 'W',\n      title: 'Weight',\n      unit: 'pound',\n      default: 1\n    },\n    {\n      name: 'S',\n      title: 'Sag/Deflection',\n      unit: 'meter',\n      default: 0.05\n    },\n    {\n      name: 'T',\n      title: 'Tension',\n      unit: 'pound-force'\n    }\n  ]\n};\n\nexport default slackline;\n"
  },
  {
    "path": "src/pages/tools/number/generic-calc/data/sphereArea.ts",
    "content": "import type { GenericCalcType } from './types';\n\nconst areaSphere: GenericCalcType = {\n  icon: 'ph:sphere-duotone',\n  keywords: ['geometry', 'radius', '3d'],\n  path: 'area-sphere',\n  formula: 'A = 4 * pi * r**2',\n  presets: [],\n  i18n: {\n    name: 'number:sphereArea.title',\n    description: 'number:sphereArea.description',\n    shortDescription: 'number:sphereArea.shortDescription',\n    longDescription: 'number:sphereArea.longDescription'\n  },\n  variables: [\n    {\n      name: 'A',\n      title: 'Area',\n      unit: 'mm2'\n    },\n    {\n      name: 'r',\n      title: 'Radius',\n      formula: 'r = sqrt(A/pi) / 2',\n      unit: 'mm',\n      default: 1\n    }\n  ]\n};\n\nexport default areaSphere;\n"
  },
  {
    "path": "src/pages/tools/number/generic-calc/data/sphereVolume.ts",
    "content": "import type { GenericCalcType } from './types';\n\nconst volumeSphere: GenericCalcType = {\n  icon: 'gravity-ui:sphere',\n  keywords: ['geometry', 'radius', 'diameter', 'capacity', '3d'],\n  i18n: {\n    name: 'number:sphereVolume.title',\n    description: 'number:sphereVolume.description',\n    shortDescription: 'number:sphereVolume.shortDescription',\n    longDescription: 'number:sphereVolume.longDescription'\n  },\n  path: 'volume-sphere',\n  formula: 'v = (4/3) * pi * r**3',\n  presets: [],\n  variables: [\n    {\n      name: 'v',\n      title: 'Volume',\n      unit: 'mm3'\n    },\n    {\n      name: 'r',\n      title: 'Radius',\n      unit: 'mm',\n      default: 1,\n      alternates: [\n        {\n          title: 'Diameter',\n          formula: 'x = 2 * v',\n          unit: 'mm'\n        }\n      ]\n    }\n  ]\n};\n\nexport default volumeSphere;\n"
  },
  {
    "path": "src/pages/tools/number/generic-calc/data/types.ts",
    "content": "import { DataTable } from '../../../../../datatables';\nimport { ToolMeta } from '@tools/defineTool';\n\nexport interface AlternativeVarInfo {\n  title: string;\n  unit: string;\n  defaultPrefix?: string;\n  formula: string;\n}\n\nexport interface GenericCalcType extends Omit<ToolMeta, 'component'> {\n  formula: string;\n  extraOutputs?: {\n    title: string;\n    formula: string;\n    unit: string;\n    // Si prefix default\n    defaultPrefix?: string;\n  }[];\n  presets?: {\n    title: string;\n    source: DataTable;\n    default: string;\n    bind: {\n      [key: string]: string;\n    };\n  }[];\n  variables: {\n    name: string;\n    title: string;\n    unit: string;\n    defaultPrefix?: string;\n    // If absence, assume it's the default target var\n    default?: number;\n\n    // If present and false,  don't allow user to select this as output\n    solvable?: boolean;\n\n    // Alternate rearrangement of the formula, to be used when calculating this.\n    // If missing, the main formula is used with auto derivation.\n    formula?: string;\n\n    // Alternates are alternate ways of entering the exact same thing,\n    // like the diameter or radius.  The formula for an alternate\n    // can use only one variable, always called v, which is the main\n    // variable it's an alternate of\n    alternates?: AlternativeVarInfo[];\n  }[];\n}\n"
  },
  {
    "path": "src/pages/tools/number/generic-calc/data/voltageDropInWire.ts",
    "content": "import type { GenericCalcType } from './types';\nimport material_electrical_properties from '../../../../../datatables/data/material_electrical_properties';\nimport wire_gauge from '../../../../../datatables/data/wire_gauge';\n\nconst voltageDropInWire: GenericCalcType = {\n  icon: 'simple-icons:wire',\n  keywords: ['awg', 'gauge', 'resistivity', 'conductor'],\n  path: 'cable-voltage-drop',\n  formula: 'x = (((p * L) / (A/10**6) ) *2) * I',\n  i18n: {\n    name: 'number:voltageDropInWire.title',\n    description: 'number:voltageDropInWire.description',\n    shortDescription: 'number:voltageDropInWire.shortDescription',\n    longDescription: 'number:voltageDropInWire.longDescription'\n  },\n  presets: [\n    {\n      title: 'Material',\n      source: material_electrical_properties,\n      default: 'Copper',\n      bind: {\n        p: 'resistivity_20c'\n      }\n    },\n\n    {\n      title: 'Wire Gauge',\n      source: wire_gauge,\n      default: '24 AWG',\n      bind: {\n        A: 'area'\n      }\n    }\n  ],\n\n  extraOutputs: [\n    {\n      title: 'Total Resistance',\n      formula: '((p * L) / (A/10**6))*2',\n      unit: 'Ω'\n    },\n    {\n      title: 'Total Power Dissipated',\n      formula: 'I**2 * (((p * L) / (A/10**6))*2)',\n      unit: 'W'\n    }\n  ],\n  variables: [\n    {\n      name: 'L',\n      title: 'Length',\n      unit: 'meter',\n      default: 1\n    },\n    {\n      name: 'A',\n      title: 'Wire Area',\n      unit: 'mm2',\n      default: 1\n    },\n\n    {\n      name: 'I',\n      title: 'Current',\n      unit: 'A',\n      default: 1\n    },\n    {\n      name: 'p',\n      title: 'Resistivity',\n      unit: 'Ω/m3',\n      default: 1,\n      defaultPrefix: 'n'\n    },\n    {\n      name: 'x',\n      title: 'Voltage Drop',\n      unit: 'V'\n    }\n  ]\n};\n\nexport default voltageDropInWire;\n"
  },
  {
    "path": "src/pages/tools/number/generic-calc/index.tsx",
    "content": "import {\n  Autocomplete,\n  Box,\n  MenuItem,\n  Radio,\n  Select,\n  Stack,\n  TextField,\n  useTheme\n} from '@mui/material';\nimport React, { useContext, useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport NumericInputWithUnit from '@components/input/NumericInputWithUnit';\nimport { UpdateField } from '@components/options/ToolOptions';\nimport { InitialValuesType } from './types';\nimport type { AlternativeVarInfo, GenericCalcType } from './data/types';\nimport { dataTableLookup } from 'datatables';\n\nimport nerdamer from 'nerdamer-prime';\nimport 'nerdamer-prime/Algebra';\nimport 'nerdamer-prime/Solve';\nimport 'nerdamer-prime/Calculus';\nimport Qty from 'js-quantities';\nimport { CustomSnackBarContext } from 'contexts/CustomSnackBarContext';\nimport Typography from '@mui/material/Typography';\nimport Grid from '@mui/material/Grid';\nimport useMediaQuery from '@mui/material/useMediaQuery';\nimport { useTranslation } from 'react-i18next';\nimport { validNamespaces } from '../../../../i18n';\n\nfunction numericSolveEquationFor(\n  equation: string,\n  varName: string,\n  variables: { [key: string]: number }\n) {\n  let expr = nerdamer(equation);\n  for (const key in variables) {\n    expr = expr.sub(key, variables[key].toString());\n  }\n\n  let result: nerdamer.Expression | nerdamer.Expression[] =\n    expr.solveFor(varName);\n\n  // Sometimes the result is an array, check for it while keeping linter happy\n  if ((result as unknown as nerdamer.Expression).toDecimal === undefined) {\n    result = (result as unknown as nerdamer.Expression[])[0];\n  }\n\n  return parseFloat(\n    (result as unknown as nerdamer.Expression).evaluate().toDecimal()\n  );\n}\n\nexport default async function makeTool(\n  calcData: GenericCalcType\n): Promise<React.JSXElementConstructor<ToolComponentProps>> {\n  const initialValues: InitialValuesType = {\n    outputVariable: '',\n    vars: {},\n    presets: {}\n  };\n\n  return function GenericCalc({ title }: ToolComponentProps) {\n    const { showSnackBar } = useContext(CustomSnackBarContext);\n    const { t } = useTranslation(validNamespaces);\n    const theme = useTheme();\n    const lessThanSmall = useMediaQuery(theme.breakpoints.down('sm'));\n\n    // For UX purposes we need to track what vars are\n    const [valsBoundToPreset, setValsBoundToPreset] = useState<{\n      [key: string]: string;\n    }>({});\n\n    const [extraOutputs, setExtraOutputs] = useState<{\n      [key: string]: number;\n    }>({});\n\n    const updateVarField = (\n      name: string,\n      value: number,\n      unit: string,\n      values: InitialValuesType,\n      updateFieldFunc: UpdateField<InitialValuesType>\n    ) => {\n      // Make copy\n      const newVars = { ...values.vars };\n      newVars[name] = {\n        value,\n        unit: unit\n      };\n      updateFieldFunc('vars', newVars);\n    };\n\n    const handleSelectedTargetChange = (\n      varName: string,\n      updateFieldFunc: UpdateField<InitialValuesType>\n    ) => {\n      updateFieldFunc('outputVariable', varName);\n    };\n\n    const handleSelectedPresetChange = (\n      selection: string,\n      preset: string,\n      currentValues: InitialValuesType,\n      updateFieldFunc: UpdateField<InitialValuesType>\n    ) => {\n      const newPresets = { ...currentValues.presets };\n      newPresets[selection] = preset;\n      updateFieldFunc('presets', newPresets);\n\n      // Clear old selection using setState callback pattern\n      setValsBoundToPreset((prevState) => {\n        const newState = { ...prevState };\n        // Remove all keys bound to this selection\n        Object.keys(newState).forEach((key) => {\n          if (newState[key] === selection) {\n            delete newState[key];\n          }\n        });\n        return newState;\n      });\n\n      const selectionData = calcData.presets?.find(\n        (sel) => sel.title === selection\n      );\n\n      if (preset && preset != '<custom>') {\n        if (selectionData) {\n          // Create an object with the new bindings\n          const newBindings: { [key: string]: string } = {};\n\n          for (const key in selectionData.bind) {\n            // Add to newBindings for later state update\n            newBindings[key] = selection;\n\n            if (currentValues.outputVariable === key) {\n              handleSelectedTargetChange('', updateFieldFunc);\n            }\n\n            updateVarField(\n              key,\n\n              dataTableLookup(selectionData.source, preset)[\n                selectionData.bind[key]\n              ],\n\n              selectionData.source.columns[selectionData.bind[key]]?.unit || '',\n              currentValues,\n              updateFieldFunc\n            );\n          }\n\n          // Update state with new bindings\n          setValsBoundToPreset((prevState) => ({\n            ...prevState,\n            ...newBindings\n          }));\n        } else {\n          throw new Error(\n            `Preset \"${preset}\" is not valid for selection \"${selection}\"`\n          );\n        }\n      }\n    };\n\n    calcData.variables.forEach((variable) => {\n      if (variable.solvable === undefined) {\n        variable.solvable = true;\n      }\n      if (variable.default === undefined) {\n        initialValues.vars[variable.name] = {\n          value: NaN,\n          unit: variable.unit\n        };\n        initialValues.outputVariable = variable.name;\n      } else {\n        initialValues.vars[variable.name] = {\n          value: variable.default || 0,\n          unit: variable.unit\n        };\n      }\n    });\n\n    calcData.presets?.forEach((selection) => {\n      initialValues.presets[selection.title] = selection.default;\n      if (selection.default == '<custom>') return;\n      for (const key in selection.bind) {\n        initialValues.vars[key] = {\n          value: dataTableLookup(selection.source, selection.default)[\n            selection.bind[key]\n          ],\n\n          unit: selection.source.columns[selection.bind[key]]?.unit || ''\n        };\n        // We'll set this in useEffect instead of directly modifying state\n      }\n    });\n\n    function getAlternate(\n      alternateInfo: AlternativeVarInfo,\n      mainInfo: GenericCalcType['variables'][number],\n      mainValue: {\n        value: number;\n        unit: string;\n      }\n    ) {\n      if (isNaN(mainValue.value)) return NaN;\n      const canonicalValue = Qty(mainValue.value, mainValue.unit).to(\n        mainInfo.unit\n      ).scalar;\n\n      return numericSolveEquationFor(alternateInfo.formula, 'x', {\n        v: canonicalValue\n      });\n    }\n\n    function getMainFromAlternate(\n      alternateInfo: AlternativeVarInfo,\n      mainInfo: GenericCalcType['variables'][number],\n      alternateValue: {\n        value: number;\n        unit: string;\n      }\n    ) {\n      if (isNaN(alternateValue.value)) return NaN;\n      const canonicalValue = Qty(alternateValue.value, alternateValue.unit).to(\n        alternateInfo.unit\n      ).scalar;\n\n      return numericSolveEquationFor(alternateInfo.formula, 'v', {\n        x: canonicalValue\n      });\n    }\n\n    return (\n      <ToolContent\n        title={title}\n        inputComponent={null}\n        initialValues={initialValues}\n        toolInfo={{\n          title: t(calcData.i18n.name),\n          description: calcData.i18n.longDescription\n            ? t(calcData.i18n.longDescription)\n            : undefined\n        }}\n        verticalGroups\n        // @ts-ignore\n        getGroups={({ values, updateField }) => [\n          ...(calcData.presets?.length\n            ? [\n                {\n                  title: 'Presets',\n                  component: (\n                    <Grid container spacing={2} maxWidth={500}>\n                      {calcData.presets?.map((preset) => (\n                        <Grid item xs={12} key={preset.title}>\n                          <Stack\n                            direction={{ xs: 'column', md: 'row' }}\n                            spacing={2}\n                            alignItems={'center'}\n                            justifyContent={'space-between'}\n                          >\n                            <Typography>{preset.title}</Typography>\n                            <Autocomplete\n                              disablePortal\n                              id=\"combo-box-demo\"\n                              value={values.presets[preset.title]}\n                              options={[\n                                '<custom>',\n                                ...Object.keys(preset.source.data).sort()\n                              ]}\n                              sx={{ width: '80%' }}\n                              onChange={(event, newValue) => {\n                                handleSelectedPresetChange(\n                                  preset.title,\n                                  newValue || '',\n                                  values,\n                                  updateField\n                                );\n                              }}\n                              renderInput={(params) => (\n                                <TextField {...params} label=\"Preset\" />\n                              )}\n                            />\n                          </Stack>\n                        </Grid>\n                      ))}\n                    </Grid>\n                  )\n                }\n              ]\n            : []),\n          {\n            title: 'Variables',\n            component: (\n              <Box>\n                {lessThanSmall ? (\n                  <Stack\n                    direction={'column'}\n                    spacing={2}\n                    alignItems={'center'}\n                    justifyContent={'space-between'}\n                  >\n                    <Typography>Solve for</Typography>\n                    <Select\n                      sx={{ width: '80%' }}\n                      fullWidth\n                      value={values.outputVariable}\n                      onChange={(event) =>\n                        handleSelectedTargetChange(\n                          event.target.value,\n                          updateField\n                        )\n                      }\n                    >\n                      {calcData.variables.map((variable) => (\n                        <MenuItem\n                          disabled={\n                            valsBoundToPreset[variable.name] !== undefined ||\n                            variable.solvable === false\n                          }\n                          key={variable.name}\n                          value={variable.name}\n                        >\n                          {variable.title}\n                        </MenuItem>\n                      ))}\n                    </Select>\n                  </Stack>\n                ) : (\n                  <Grid container spacing={2} sx={{ mb: 2 }}>\n                    <Grid item xs={10}></Grid>\n                    <Grid item xs={2}>\n                      <Typography fontWeight=\"bold\" align=\"center\">\n                        Solve For\n                      </Typography>\n                    </Grid>\n                  </Grid>\n                )}\n                {calcData.variables.map((variable) => (\n                  <Box\n                    key={variable.name}\n                    sx={{\n                      my: 3,\n                      p: 1,\n                      borderRadius: 1\n                    }}\n                  >\n                    <Grid container spacing={2} alignItems=\"center\">\n                      <Grid item xs={lessThanSmall ? 12 : 10}>\n                        <Box>\n                          <Stack spacing={2}>\n                            <Stack\n                              direction={{ xs: 'column', md: 'row' }}\n                              spacing={2}\n                              alignItems=\"center\"\n                            >\n                              <Typography sx={{ minWidth: '8%' }}>\n                                {variable.title}\n                              </Typography>\n                              <NumericInputWithUnit\n                                defaultPrefix={variable.defaultPrefix}\n                                value={values.vars[variable.name]}\n                                disabled={\n                                  values.outputVariable === variable.name ||\n                                  valsBoundToPreset[variable.name] !== undefined\n                                }\n                                disableChangingUnit={\n                                  valsBoundToPreset[variable.name] !== undefined\n                                }\n                                onOwnChange={(val) =>\n                                  updateVarField(\n                                    variable.name,\n                                    val.value,\n                                    val.unit,\n                                    values,\n                                    updateField\n                                  )\n                                }\n                              />\n                            </Stack>\n\n                            {variable.alternates?.map((alt) => (\n                              <Box key={alt.title}>\n                                <Stack\n                                  direction=\"row\"\n                                  spacing={2}\n                                  alignItems=\"center\"\n                                >\n                                  <Typography sx={{ minWidth: '8%' }}>\n                                    {alt.title}\n                                  </Typography>\n                                  <Box sx={{ flexGrow: 1 }}>\n                                    <NumericInputWithUnit\n                                      key={alt.title}\n                                      defaultPrefix={alt.defaultPrefix || ''}\n                                      value={{\n                                        value:\n                                          getAlternate(\n                                            alt,\n                                            variable,\n                                            values.vars[variable.name]\n                                          ) || NaN,\n                                        unit: alt.unit || ''\n                                      }}\n                                      disabled={\n                                        values.outputVariable ===\n                                          variable.name ||\n                                        valsBoundToPreset[variable.name] !==\n                                          undefined\n                                      }\n                                      disableChangingUnit={\n                                        valsBoundToPreset[variable.name] !==\n                                        undefined\n                                      }\n                                      onOwnChange={(val) =>\n                                        updateVarField(\n                                          variable.name,\n                                          getMainFromAlternate(\n                                            alt,\n                                            variable,\n                                            val\n                                          ),\n                                          variable.unit,\n                                          values,\n                                          updateField\n                                        )\n                                      }\n                                    />\n                                  </Box>\n                                </Stack>\n                              </Box>\n                            ))}\n                          </Stack>\n                        </Box>\n                      </Grid>\n\n                      {!lessThanSmall && (\n                        <Grid\n                          item\n                          xs={2}\n                          sx={{ display: 'flex', justifyContent: 'center' }}\n                        >\n                          <Radio\n                            value={variable.name}\n                            checked={values.outputVariable === variable.name}\n                            disabled={\n                              valsBoundToPreset[variable.name] !== undefined ||\n                              variable.solvable === false\n                            }\n                            onClick={() =>\n                              handleSelectedTargetChange(\n                                variable.name,\n                                updateField\n                              )\n                            }\n                          />\n                        </Grid>\n                      )}\n                    </Grid>\n                  </Box>\n                ))}\n              </Box>\n            )\n          },\n          ...(calcData.extraOutputs\n            ? [\n                {\n                  title: 'Extra outputs',\n                  component: (\n                    <Box>\n                      <Grid container spacing={2}>\n                        {calcData.extraOutputs?.map((extraOutput) => (\n                          <Grid item xs={12} key={extraOutput.title}>\n                            <Stack spacing={1}>\n                              <Typography>{extraOutput.title}</Typography>\n                              <NumericInputWithUnit\n                                disabled={true}\n                                defaultPrefix={extraOutput.defaultPrefix}\n                                value={{\n                                  value: extraOutputs[extraOutput.title],\n                                  unit: extraOutput.unit\n                                }}\n                              />\n                            </Stack>\n                          </Grid>\n                        ))}\n                      </Grid>\n                    </Box>\n                  )\n                }\n              ]\n            : [])\n        ]}\n        compute={(values) => {\n          if (values.outputVariable === '') {\n            showSnackBar('Please select a solve for variable', 'error');\n            return;\n          }\n          let expr: nerdamer.Expression | null = null;\n\n          for (const variable of calcData.variables) {\n            if (variable.name === values.outputVariable) {\n              if (variable.formula !== undefined) {\n                expr = nerdamer(variable.formula);\n              }\n            }\n          }\n\n          if (expr == null) {\n            expr = nerdamer(calcData.formula);\n          }\n          if (expr == null) {\n            throw new Error('No formula found');\n          }\n\n          Object.keys(values.vars).forEach((key) => {\n            if (key === values.outputVariable) return;\n            if (expr === null) {\n              throw new Error('Math fail');\n            }\n            expr = expr.sub(key, values.vars[key].value.toString());\n          });\n\n          let result: nerdamer.Expression | nerdamer.Expression[] =\n            expr.solveFor(values.outputVariable);\n\n          // Sometimes the result is an array\n          if (\n            (result as unknown as nerdamer.Expression).toDecimal === undefined\n          ) {\n            if ((result as unknown as nerdamer.Expression[])?.length < 1) {\n              values.vars[values.outputVariable].value = NaN;\n              if (calcData.extraOutputs !== undefined) {\n                // Update extraOutputs using setState\n                setExtraOutputs((prevState) => {\n                  const newState = { ...prevState };\n                  for (let i = 0; i < calcData.extraOutputs!.length; i++) {\n                    const extraOutput = calcData.extraOutputs![i];\n                    newState[extraOutput.title] = NaN;\n                  }\n                  return newState;\n                });\n              }\n              throw new Error('No solution found for this input');\n            }\n            result = (result as unknown as nerdamer.Expression[])[0];\n          }\n\n          if (result) {\n            if (values.vars[values.outputVariable] != undefined) {\n              values.vars[values.outputVariable].value = parseFloat(\n                (result as unknown as nerdamer.Expression)\n                  .evaluate()\n                  .toDecimal()\n              );\n            }\n          } else {\n            values.vars[values.outputVariable].value = NaN;\n          }\n\n          if (calcData.extraOutputs !== undefined) {\n            for (let i = 0; i < calcData.extraOutputs.length; i++) {\n              const extraOutput = calcData.extraOutputs[i];\n\n              let expr = nerdamer(extraOutput.formula);\n\n              Object.keys(values.vars).forEach((key) => {\n                expr = expr.sub(key, values.vars[key].value.toString());\n              });\n\n              // todo could this have multiple solutions too?\n              const result: nerdamer.Expression = expr.evaluate();\n\n              if (result) {\n                // Update extraOutputs state properly\n                setExtraOutputs((prevState) => ({\n                  ...prevState,\n                  [extraOutput.title]: parseFloat(result.toDecimal())\n                }));\n              }\n            }\n          }\n        }}\n      />\n    );\n  };\n}\n"
  },
  {
    "path": "src/pages/tools/number/generic-calc/meta.ts",
    "content": "import { DefinedTool, defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\nimport type { GenericCalcType } from './data/types';\nimport allGenericCalcs from './data/index';\n\nasync function importComponent(data: GenericCalcType) {\n  const x = await import('./index');\n  return { default: await x.default(data) };\n}\n\nconst tools: DefinedTool[] = [];\n\nallGenericCalcs.forEach((x) => {\n  async function importComponent2() {\n    return await importComponent(x);\n  }\n\n  tools.push(\n    defineTool('number', {\n      ...x,\n      path: 'generic-calc/' + x.path,\n      keywords: [...x.keywords],\n      component: lazy(importComponent2)\n    })\n  );\n});\n\nexport { tools };\n"
  },
  {
    "path": "src/pages/tools/number/generic-calc/types.ts",
    "content": "export type InitialValuesType = {\n  vars: {\n    [key: string]: {\n      value: number;\n      unit: string;\n    };\n  };\n\n  // Track preset selections\n  presets: {\n    [key: string]: string;\n  };\n  outputVariable: string;\n};\n"
  },
  {
    "path": "src/pages/tools/number/index.ts",
    "content": "import { tool as numberRandomPortGenerator } from './random-port-generator/meta';\nimport { tool as numberRandomNumberGenerator } from './random-number-generator/meta';\nimport { tool as numberSum } from './sum/meta';\nimport { tool as numberGenerate } from './generate/meta';\nimport { tool as numberArithmeticSequence } from './arithmetic-sequence/meta';\nimport { tool as numberByteConverter } from './byte-converter/meta';\nimport { tools as genericCalcTools } from './generic-calc/meta';\n\nexport const numberTools = [\n  numberSum,\n  numberGenerate,\n  numberArithmeticSequence,\n  numberRandomPortGenerator,\n  numberRandomNumberGenerator,\n  numberByteConverter,\n  ...genericCalcTools\n];\n"
  },
  {
    "path": "src/pages/tools/number/random-number-generator/index.tsx",
    "content": "import { Alert, Box } from '@mui/material';\nimport { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { formatNumbers, generateRandomNumbers, validateInput } from './service';\nimport { InitialValuesType, RandomNumberResult } from './types';\nimport { useTranslation } from 'react-i18next';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\n\nconst initialValues: InitialValuesType = {\n  minValue: 1,\n  maxValue: 100,\n  count: 10,\n  allowDecimals: false,\n  allowDuplicates: true,\n  sortResults: false,\n  separator: ', '\n};\n\nexport default function RandomNumberGenerator({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('number');\n  const [result, setResult] = useState<RandomNumberResult | null>(null);\n  const [error, setError] = useState<string | null>(null);\n  const [formattedResult, setFormattedResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType) => {\n    try {\n      setError(null);\n      setResult(null);\n      setFormattedResult('');\n\n      // Validate input\n      const validationError = validateInput(values);\n      if (validationError) {\n        setError(validationError);\n        return;\n      }\n\n      // Generate random numbers\n      const randomResult = generateRandomNumbers(values);\n      setResult(randomResult);\n\n      // Format for display\n      const formatted = formatNumbers(\n        randomResult.numbers,\n        values.separator,\n        values.allowDecimals\n      );\n      setFormattedResult(formatted);\n    } catch (err) {\n      console.error('Random number generation failed:', err);\n      setError(t('randomNumberGenerator.error.generationFailed'));\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('randomNumberGenerator.options.range.title'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.minValue.toString()}\n            onOwnChange={(value) =>\n              updateField('minValue', parseInt(value) || 1)\n            }\n            description={t(\n              'randomNumberGenerator.options.range.minDescription'\n            )}\n            inputProps={{\n              type: 'number',\n              'data-testid': 'min-value-input'\n            }}\n          />\n          <TextFieldWithDesc\n            value={values.maxValue.toString()}\n            onOwnChange={(value) =>\n              updateField('maxValue', parseInt(value) || 100)\n            }\n            description={t(\n              'randomNumberGenerator.options.range.maxDescription'\n            )}\n            inputProps={{\n              type: 'number',\n              'data-testid': 'max-value-input'\n            }}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('randomNumberGenerator.options.generation.title'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.count.toString()}\n            onOwnChange={(value) => updateField('count', parseInt(value) || 10)}\n            description={t(\n              'randomNumberGenerator.options.generation.countDescription'\n            )}\n            inputProps={{\n              type: 'number',\n              min: 1,\n              max: 10000,\n              'data-testid': 'count-input'\n            }}\n          />\n\n          <CheckboxWithDesc\n            title={t(\n              'randomNumberGenerator.options.generation.allowDecimals.title'\n            )}\n            checked={values.allowDecimals}\n            onChange={(value) => updateField('allowDecimals', value)}\n            description={t(\n              'randomNumberGenerator.options.generation.allowDecimals.description'\n            )}\n          />\n\n          <CheckboxWithDesc\n            title={t(\n              'randomNumberGenerator.options.generation.allowDuplicates.title'\n            )}\n            checked={values.allowDuplicates}\n            onChange={(value) => updateField('allowDuplicates', value)}\n            description={t(\n              'randomNumberGenerator.options.generation.allowDuplicates.description'\n            )}\n          />\n\n          <CheckboxWithDesc\n            title={t(\n              'randomNumberGenerator.options.generation.sortResults.title'\n            )}\n            checked={values.sortResults}\n            onChange={(value) => updateField('sortResults', value)}\n            description={t(\n              'randomNumberGenerator.options.generation.sortResults.description'\n            )}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('randomNumberGenerator.options.output.title'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.separator}\n            onOwnChange={(value) => updateField('separator', value)}\n            description={t(\n              'randomNumberGenerator.options.output.separatorDescription'\n            )}\n            inputProps={{\n              'data-testid': 'separator-input'\n            }}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      compute={compute}\n      getGroups={getGroups}\n      resultComponent={\n        <Box>\n          {error && (\n            <Alert severity=\"error\" sx={{ mb: 2 }}>\n              {error}\n            </Alert>\n          )}\n\n          {result && (\n            <ToolTextResult\n              title={t('randomNumberGenerator.result.title')}\n              value={formattedResult}\n            />\n          )}\n        </Box>\n      }\n      toolInfo={{\n        title: t('randomNumberGenerator.info.title'),\n        description:\n          longDescription || t('randomNumberGenerator.info.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/number/random-number-generator/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('number', {\n  i18n: {\n    name: 'number:randomNumberGenerator.title',\n    description: 'number:randomNumberGenerator.description',\n    shortDescription: 'number:randomNumberGenerator.shortDescription',\n    longDescription: 'number:randomNumberGenerator.longDescription',\n    userTypes: ['generalUsers']\n  },\n  path: 'random-number-generator',\n  icon: 'mdi:dice-multiple',\n  keywords: [\n    'random',\n    'number',\n    'generator',\n    'range',\n    'min',\n    'max',\n    'integer',\n    'decimal',\n    'float'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/number/random-number-generator/random-number-generator.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { generateRandomNumbers, validateInput, formatNumbers } from './service';\nimport { InitialValuesType } from './types';\n\ndescribe('Random Number Generator Service', () => {\n  describe('generateRandomNumbers', () => {\n    it('should generate random numbers within the specified range', () => {\n      const options: InitialValuesType = {\n        minValue: 1,\n        maxValue: 10,\n        count: 5,\n        allowDecimals: false,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = generateRandomNumbers(options);\n\n      expect(result.numbers).toHaveLength(5);\n      expect(result.min).toBe(1);\n      expect(result.max).toBe(10);\n      expect(result.count).toBe(5);\n\n      // Check that all numbers are within range\n      result.numbers.forEach((num) => {\n        expect(num).toBeGreaterThanOrEqual(1);\n        expect(num).toBeLessThanOrEqual(10);\n        expect(Number.isInteger(num)).toBe(true);\n      });\n    });\n\n    it('should generate decimal numbers when allowDecimals is true', () => {\n      const options: InitialValuesType = {\n        minValue: 0,\n        maxValue: 1,\n        count: 3,\n        allowDecimals: true,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = generateRandomNumbers(options);\n\n      expect(result.numbers).toHaveLength(3);\n\n      // Check that numbers are within range and can be decimals\n      result.numbers.forEach((num) => {\n        expect(num).toBeGreaterThanOrEqual(0);\n        expect(num).toBeLessThanOrEqual(1);\n      });\n    });\n\n    it('should generate unique numbers when allowDuplicates is false', () => {\n      const options: InitialValuesType = {\n        minValue: 1,\n        maxValue: 5,\n        count: 3,\n        allowDecimals: false,\n        allowDuplicates: false,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = generateRandomNumbers(options);\n\n      expect(result.numbers).toHaveLength(3);\n\n      // Check for uniqueness\n      const uniqueNumbers = new Set(result.numbers);\n      expect(uniqueNumbers.size).toBe(3);\n    });\n\n    it('should sort results when sortResults is true', () => {\n      const options: InitialValuesType = {\n        minValue: 1,\n        maxValue: 10,\n        count: 5,\n        allowDecimals: false,\n        allowDuplicates: true,\n        sortResults: true,\n        separator: ', '\n      };\n\n      const result = generateRandomNumbers(options);\n\n      expect(result.numbers).toHaveLength(5);\n      expect(result.isSorted).toBe(true);\n\n      // Check that numbers are sorted\n      for (let i = 1; i < result.numbers.length; i++) {\n        expect(result.numbers[i]).toBeGreaterThanOrEqual(result.numbers[i - 1]);\n      }\n    });\n\n    it('should throw error when minValue >= maxValue', () => {\n      const options: InitialValuesType = {\n        minValue: 10,\n        maxValue: 5,\n        count: 5,\n        allowDecimals: false,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      expect(() => generateRandomNumbers(options)).toThrow(\n        'Minimum value must be less than maximum value'\n      );\n    });\n\n    it('should throw error when count <= 0', () => {\n      const options: InitialValuesType = {\n        minValue: 1,\n        maxValue: 10,\n        count: 0,\n        allowDecimals: false,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      expect(() => generateRandomNumbers(options)).toThrow(\n        'Count must be greater than 0'\n      );\n    });\n\n    it('should throw error when unique count exceeds available range', () => {\n      const options: InitialValuesType = {\n        minValue: 1,\n        maxValue: 5,\n        count: 10,\n        allowDecimals: false,\n        allowDuplicates: false,\n        sortResults: false,\n        separator: ', '\n      };\n\n      expect(() => generateRandomNumbers(options)).toThrow(\n        'Cannot generate unique numbers: count exceeds available range'\n      );\n    });\n  });\n\n  describe('validateInput', () => {\n    it('should return null for valid input', () => {\n      const options: InitialValuesType = {\n        minValue: 1,\n        maxValue: 10,\n        count: 5,\n        allowDecimals: false,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = validateInput(options);\n      expect(result).toBeNull();\n    });\n\n    it('should return error when minValue >= maxValue', () => {\n      const options: InitialValuesType = {\n        minValue: 10,\n        maxValue: 5,\n        count: 5,\n        allowDecimals: false,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = validateInput(options);\n      expect(result).toBe('Minimum value must be less than maximum value');\n    });\n\n    it('should return error when count <= 0', () => {\n      const options: InitialValuesType = {\n        minValue: 1,\n        maxValue: 10,\n        count: 0,\n        allowDecimals: false,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = validateInput(options);\n      expect(result).toBe('Count must be greater than 0');\n    });\n\n    it('should return error when count > 10000', () => {\n      const options: InitialValuesType = {\n        minValue: 1,\n        maxValue: 10,\n        count: 10001,\n        allowDecimals: false,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = validateInput(options);\n      expect(result).toBe('Count cannot exceed 10,000');\n    });\n\n    it('should return error when range > 1000000', () => {\n      const options: InitialValuesType = {\n        minValue: 1,\n        maxValue: 1000002,\n        count: 5,\n        allowDecimals: false,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = validateInput(options);\n      expect(result).toBe('Range cannot exceed 1,000,000');\n    });\n  });\n\n  describe('formatNumbers', () => {\n    it('should format integers correctly', () => {\n      const numbers = [1, 2, 3, 4, 5];\n      const result = formatNumbers(numbers, ', ', false);\n      expect(result).toBe('1, 2, 3, 4, 5');\n    });\n\n    it('should format decimals correctly', () => {\n      const numbers = [1.5, 2.7, 3.2];\n      const result = formatNumbers(numbers, ' | ', true);\n      expect(result).toBe('1.50 | 2.70 | 3.20');\n    });\n\n    it('should handle custom separators', () => {\n      const numbers = [1, 2, 3];\n      const result = formatNumbers(numbers, ' -> ', false);\n      expect(result).toBe('1 -> 2 -> 3');\n    });\n\n    it('should handle empty array', () => {\n      const numbers: number[] = [];\n      const result = formatNumbers(numbers, ', ', false);\n      expect(result).toBe('');\n    });\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/number/random-number-generator/service.ts",
    "content": "import { InitialValuesType, RandomNumberResult } from './types';\n\n/**\n * Generate random numbers within a specified range\n */\nexport function generateRandomNumbers(\n  options: InitialValuesType\n): RandomNumberResult {\n  const {\n    minValue,\n    maxValue,\n    count,\n    allowDecimals,\n    allowDuplicates,\n    sortResults\n  } = options;\n\n  if (minValue >= maxValue) {\n    throw new Error('Minimum value must be less than maximum value');\n  }\n\n  if (count <= 0) {\n    throw new Error('Count must be greater than 0');\n  }\n\n  if (!allowDuplicates && count > maxValue - minValue + 1) {\n    throw new Error(\n      'Cannot generate unique numbers: count exceeds available range'\n    );\n  }\n\n  const numbers: number[] = [];\n\n  if (allowDuplicates) {\n    // Generate random numbers with possible duplicates\n    for (let i = 0; i < count; i++) {\n      const randomNumber = generateRandomNumber(\n        minValue,\n        maxValue,\n        allowDecimals\n      );\n      numbers.push(randomNumber);\n    }\n  } else {\n    // Generate unique random numbers\n    const availableNumbers = new Set<number>();\n\n    // Create a pool of available numbers\n    for (let i = minValue; i <= maxValue; i++) {\n      if (allowDecimals) {\n        // For decimals, we need to generate more granular values\n        for (let j = 0; j < 100; j++) {\n          availableNumbers.add(i + j / 100);\n        }\n      } else {\n        availableNumbers.add(i);\n      }\n    }\n\n    const availableArray = Array.from(availableNumbers);\n\n    // Shuffle the available numbers\n    for (let i = availableArray.length - 1; i > 0; i--) {\n      const j = Math.floor(Math.random() * (i + 1));\n      [availableArray[i], availableArray[j]] = [\n        availableArray[j],\n        availableArray[i]\n      ];\n    }\n\n    // Take the first 'count' numbers\n    for (let i = 0; i < Math.min(count, availableArray.length); i++) {\n      numbers.push(availableArray[i]);\n    }\n  }\n\n  // Sort if requested\n  if (sortResults) {\n    numbers.sort((a, b) => a - b);\n  }\n\n  return {\n    numbers,\n    min: minValue,\n    max: maxValue,\n    count,\n    hasDuplicates: !allowDuplicates && hasDuplicatesInArray(numbers),\n    isSorted: sortResults\n  };\n}\n\n/**\n * Generate a single random number within the specified range\n */\nfunction generateRandomNumber(\n  min: number,\n  max: number,\n  allowDecimals: boolean\n): number {\n  if (allowDecimals) {\n    return Math.random() * (max - min) + min;\n  } else {\n    return Math.floor(Math.random() * (max - min + 1)) + min;\n  }\n}\n\n/**\n * Check if an array has duplicate values\n */\nfunction hasDuplicatesInArray(arr: number[]): boolean {\n  const seen = new Set<number>();\n  for (const num of arr) {\n    if (seen.has(num)) {\n      return true;\n    }\n    seen.add(num);\n  }\n  return false;\n}\n\n/**\n * Format numbers for display\n */\nexport function formatNumbers(\n  numbers: number[],\n  separator: string,\n  allowDecimals: boolean\n): string {\n  return numbers\n    .map((num) => (allowDecimals ? num.toFixed(2) : Math.round(num).toString()))\n    .join(separator);\n}\n\n/**\n * Validate input parameters\n */\nexport function validateInput(options: InitialValuesType): string | null {\n  const { minValue, maxValue, count } = options;\n\n  if (minValue >= maxValue) {\n    return 'Minimum value must be less than maximum value';\n  }\n\n  if (count <= 0) {\n    return 'Count must be greater than 0';\n  }\n\n  if (count > 10000) {\n    return 'Count cannot exceed 10,000';\n  }\n\n  if (maxValue - minValue > 1000000) {\n    return 'Range cannot exceed 1,000,000';\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "src/pages/tools/number/random-number-generator/types.ts",
    "content": "export type InitialValuesType = {\n  minValue: number;\n  maxValue: number;\n  count: number;\n  allowDecimals: boolean;\n  allowDuplicates: boolean;\n  sortResults: boolean;\n  separator: string;\n};\n\nexport type RandomNumberResult = {\n  numbers: number[];\n  min: number;\n  max: number;\n  count: number;\n  hasDuplicates: boolean;\n  isSorted: boolean;\n};\n"
  },
  {
    "path": "src/pages/tools/number/random-port-generator/index.tsx",
    "content": "import { Alert, Box } from '@mui/material';\nimport { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport {\n  formatPorts,\n  generateRandomPorts,\n  getPortRangeInfo,\n  validateInput\n} from './service';\nimport { InitialValuesType, RandomPortResult } from './types';\nimport { useTranslation } from 'react-i18next';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport SimpleRadio from '@components/options/SimpleRadio';\n\nconst initialValues: InitialValuesType = {\n  portRange: 'registered',\n  minPort: 1024,\n  maxPort: 49151,\n  count: 5,\n  allowDuplicates: false,\n  sortResults: false,\n  separator: ', '\n};\n\nexport default function RandomPortGenerator({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('number');\n  const [result, setResult] = useState<RandomPortResult | null>(null);\n  const [error, setError] = useState<string | null>(null);\n  const [formattedResult, setFormattedResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType) => {\n    try {\n      setError(null);\n      setResult(null);\n      setFormattedResult('');\n\n      // Validate input\n      const validationError = validateInput(values);\n      if (validationError) {\n        setError(validationError);\n        return;\n      }\n\n      // Generate random ports\n      const randomResult = generateRandomPorts(values);\n      setResult(randomResult);\n\n      // Format for display\n      const formatted = formatPorts(randomResult.ports, values.separator);\n      setFormattedResult(formatted);\n    } catch (err) {\n      console.error('Random port generation failed:', err);\n      setError(t('randomPortGenerator.error.generationFailed'));\n    }\n  };\n  const portOptions = [\n    {\n      value: 'well-known',\n      label: t('randomPortGenerator.options.range.wellKnown')\n    },\n    {\n      value: 'registered',\n      label: t('randomPortGenerator.options.range.registered')\n    },\n    {\n      value: 'dynamic',\n      label: t('randomPortGenerator.options.range.dynamic')\n    },\n    {\n      value: 'custom',\n      label: t('randomPortGenerator.options.range.custom')\n    }\n  ] as const;\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('randomPortGenerator.options.range.title'),\n      component: (\n        <Box>\n          {portOptions.map((option) => (\n            <SimpleRadio\n              key={option.value}\n              title={option.label}\n              checked={values.portRange === option.value}\n              onClick={() => updateField('portRange', option.value)}\n            />\n          ))}\n\n          {values.portRange === 'custom' && (\n            <Box sx={{ mt: 2 }}>\n              <TextFieldWithDesc\n                value={values.minPort.toString()}\n                onOwnChange={(value) =>\n                  updateField('minPort', parseInt(value) || 1024)\n                }\n                description={t(\n                  'randomPortGenerator.options.range.minPortDescription'\n                )}\n                inputProps={{\n                  type: 'number',\n                  min: 1,\n                  max: 65535,\n                  'data-testid': 'min-port-input'\n                }}\n              />\n              <TextFieldWithDesc\n                value={values.maxPort.toString()}\n                onOwnChange={(value) =>\n                  updateField('maxPort', parseInt(value) || 49151)\n                }\n                description={t(\n                  'randomPortGenerator.options.range.maxPortDescription'\n                )}\n                inputProps={{\n                  type: 'number',\n                  min: 1,\n                  max: 65535,\n                  'data-testid': 'max-port-input'\n                }}\n              />\n            </Box>\n          )}\n\n          <Box\n            sx={{ mt: 2, p: 2, bgcolor: 'background.paper', borderRadius: 1 }}\n          >\n            <strong>{getPortRangeInfo(values.portRange).name}</strong>\n            <br />\n            {getPortRangeInfo(values.portRange).description}\n          </Box>\n        </Box>\n      )\n    },\n    {\n      title: t('randomPortGenerator.options.generation.title'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.count.toString()}\n            onOwnChange={(value) => updateField('count', parseInt(value) || 5)}\n            description={t(\n              'randomPortGenerator.options.generation.countDescription'\n            )}\n            inputProps={{\n              type: 'number',\n              min: 1,\n              max: 1000,\n              'data-testid': 'count-input'\n            }}\n          />\n\n          <CheckboxWithDesc\n            title={t(\n              'randomPortGenerator.options.generation.allowDuplicates.title'\n            )}\n            checked={values.allowDuplicates}\n            onChange={(value) => updateField('allowDuplicates', value)}\n            description={t(\n              'randomPortGenerator.options.generation.allowDuplicates.description'\n            )}\n          />\n\n          <CheckboxWithDesc\n            title={t(\n              'randomPortGenerator.options.generation.sortResults.title'\n            )}\n            checked={values.sortResults}\n            onChange={(value) => updateField('sortResults', value)}\n            description={t(\n              'randomPortGenerator.options.generation.sortResults.description'\n            )}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('randomPortGenerator.options.output.title'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.separator}\n            onOwnChange={(value) => updateField('separator', value)}\n            description={t(\n              'randomPortGenerator.options.output.separatorDescription'\n            )}\n            inputProps={{\n              'data-testid': 'separator-input'\n            }}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      compute={compute}\n      getGroups={getGroups}\n      resultComponent={\n        <Box>\n          {error && (\n            <Alert severity=\"error\" sx={{ mb: 2 }}>\n              {error}\n            </Alert>\n          )}\n\n          {result && (\n            <ToolTextResult\n              title={t('randomPortGenerator.result.title')}\n              value={formattedResult}\n            />\n          )}\n        </Box>\n      }\n      toolInfo={{\n        title: t('randomPortGenerator.info.title'),\n        description:\n          longDescription || t('randomPortGenerator.info.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/number/random-port-generator/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('number', {\n  i18n: {\n    name: 'number:randomPortGenerator.title',\n    description: 'number:randomPortGenerator.description',\n    shortDescription: 'number:randomPortGenerator.shortDescription',\n    longDescription: 'number:randomPortGenerator.longDescription',\n    userTypes: ['developers']\n  },\n  path: 'random-port-generator',\n  icon: 'mdi:network',\n  keywords: [\n    'random',\n    'port',\n    'generator',\n    'network',\n    'tcp',\n    'udp',\n    'server',\n    'client',\n    'development'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/number/random-port-generator/random-port-generator.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport {\n  generateRandomPorts,\n  validateInput,\n  formatPorts,\n  getPortRangeInfo,\n  isCommonPort,\n  getPortService,\n  PORT_RANGES\n} from './service';\nimport { InitialValuesType } from './types';\n\ndescribe('Random Port Generator Service', () => {\n  describe('generateRandomPorts', () => {\n    it('should generate random ports within the well-known range', () => {\n      const options: InitialValuesType = {\n        portRange: 'well-known',\n        minPort: 1,\n        maxPort: 1023,\n        count: 5,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = generateRandomPorts(options);\n\n      expect(result.ports).toHaveLength(5);\n      expect(result.range.min).toBe(1);\n      expect(result.range.max).toBe(1023);\n      expect(result.count).toBe(5);\n\n      // Check that all ports are within range\n      result.ports.forEach((port) => {\n        expect(port).toBeGreaterThanOrEqual(1);\n        expect(port).toBeLessThanOrEqual(1023);\n        expect(Number.isInteger(port)).toBe(true);\n      });\n    });\n\n    it('should generate random ports within the registered range', () => {\n      const options: InitialValuesType = {\n        portRange: 'registered',\n        minPort: 1024,\n        maxPort: 49151,\n        count: 3,\n        allowDuplicates: false,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = generateRandomPorts(options);\n\n      expect(result.ports).toHaveLength(3);\n      expect(result.range.min).toBe(1024);\n      expect(result.range.max).toBe(49151);\n\n      // Check for uniqueness\n      const uniquePorts = new Set(result.ports);\n      expect(uniquePorts.size).toBe(3);\n    });\n\n    it('should generate random ports within custom range', () => {\n      const options: InitialValuesType = {\n        portRange: 'custom',\n        minPort: 8000,\n        maxPort: 8100,\n        count: 4,\n        allowDuplicates: true,\n        sortResults: true,\n        separator: ', '\n      };\n\n      const result = generateRandomPorts(options);\n\n      expect(result.ports).toHaveLength(4);\n      expect(result.range.min).toBe(8000);\n      expect(result.range.max).toBe(8100);\n      expect(result.isSorted).toBe(true);\n\n      // Check that numbers are sorted\n      for (let i = 1; i < result.ports.length; i++) {\n        expect(result.ports[i]).toBeGreaterThanOrEqual(result.ports[i - 1]);\n      }\n    });\n\n    it('should throw error when minPort >= maxPort', () => {\n      const options: InitialValuesType = {\n        portRange: 'custom',\n        minPort: 1000,\n        maxPort: 500,\n        count: 5,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      expect(() => generateRandomPorts(options)).toThrow(\n        'Minimum port must be less than maximum port'\n      );\n    });\n\n    it('should throw error when count <= 0', () => {\n      const options: InitialValuesType = {\n        portRange: 'registered',\n        minPort: 1024,\n        maxPort: 49151,\n        count: 0,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      expect(() => generateRandomPorts(options)).toThrow(\n        'Count must be greater than 0'\n      );\n    });\n\n    it('should throw error when ports are outside valid range', () => {\n      const options: InitialValuesType = {\n        portRange: 'custom',\n        minPort: 0,\n        maxPort: 70000,\n        count: 5,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      expect(() => generateRandomPorts(options)).toThrow(\n        'Ports must be between 1 and 65535'\n      );\n    });\n\n    it('should throw error when unique count exceeds available range', () => {\n      const options: InitialValuesType = {\n        portRange: 'custom',\n        minPort: 1,\n        maxPort: 5,\n        count: 10,\n        allowDuplicates: false,\n        sortResults: false,\n        separator: ', '\n      };\n\n      expect(() => generateRandomPorts(options)).toThrow(\n        'Cannot generate unique ports: count exceeds available range'\n      );\n    });\n  });\n\n  describe('validateInput', () => {\n    it('should return null for valid input', () => {\n      const options: InitialValuesType = {\n        portRange: 'registered',\n        minPort: 1024,\n        maxPort: 49151,\n        count: 5,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = validateInput(options);\n      expect(result).toBeNull();\n    });\n\n    it('should return error when count <= 0', () => {\n      const options: InitialValuesType = {\n        portRange: 'registered',\n        minPort: 1024,\n        maxPort: 49151,\n        count: 0,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = validateInput(options);\n      expect(result).toBe('Count must be greater than 0');\n    });\n\n    it('should return error when count > 1000', () => {\n      const options: InitialValuesType = {\n        portRange: 'registered',\n        minPort: 1024,\n        maxPort: 49151,\n        count: 1001,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = validateInput(options);\n      expect(result).toBe('Count cannot exceed 1,000');\n    });\n\n    it('should return error when custom range has invalid ports', () => {\n      const options: InitialValuesType = {\n        portRange: 'custom',\n        minPort: 0,\n        maxPort: 70000,\n        count: 5,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = validateInput(options);\n      expect(result).toBe('Ports must be between 1 and 65535');\n    });\n\n    it('should return error when custom range has minPort >= maxPort', () => {\n      const options: InitialValuesType = {\n        portRange: 'custom',\n        minPort: 1000,\n        maxPort: 500,\n        count: 5,\n        allowDuplicates: true,\n        sortResults: false,\n        separator: ', '\n      };\n\n      const result = validateInput(options);\n      expect(result).toBe('Minimum port must be less than maximum port');\n    });\n  });\n\n  describe('formatPorts', () => {\n    it('should format ports correctly', () => {\n      const ports = [80, 443, 8080, 3000];\n      const result = formatPorts(ports, ', ');\n      expect(result).toBe('80, 443, 8080, 3000');\n    });\n\n    it('should handle custom separators', () => {\n      const ports = [80, 443, 8080];\n      const result = formatPorts(ports, ' -> ');\n      expect(result).toBe('80 -> 443 -> 8080');\n    });\n\n    it('should handle empty array', () => {\n      const ports: number[] = [];\n      const result = formatPorts(ports, ', ');\n      expect(result).toBe('');\n    });\n  });\n\n  describe('getPortRangeInfo', () => {\n    it('should return correct port range info for well-known', () => {\n      const result = getPortRangeInfo('well-known');\n      expect(result.name).toBe('Well-Known Ports');\n      expect(result.min).toBe(1);\n      expect(result.max).toBe(1023);\n    });\n\n    it('should return correct port range info for registered', () => {\n      const result = getPortRangeInfo('registered');\n      expect(result.name).toBe('Registered Ports');\n      expect(result.min).toBe(1024);\n      expect(result.max).toBe(49151);\n    });\n\n    it('should return correct port range info for dynamic', () => {\n      const result = getPortRangeInfo('dynamic');\n      expect(result.name).toBe('Dynamic Ports');\n      expect(result.min).toBe(49152);\n      expect(result.max).toBe(65535);\n    });\n\n    it('should return custom range for unknown range', () => {\n      const result = getPortRangeInfo('unknown');\n      expect(result.name).toBe('Custom Range');\n    });\n  });\n\n  describe('isCommonPort', () => {\n    it('should identify common ports correctly', () => {\n      expect(isCommonPort(80)).toBe(true);\n      expect(isCommonPort(443)).toBe(true);\n      expect(isCommonPort(22)).toBe(true);\n      expect(isCommonPort(3306)).toBe(true);\n    });\n\n    it('should return false for uncommon ports', () => {\n      expect(isCommonPort(12345)).toBe(false);\n      expect(isCommonPort(54321)).toBe(false);\n    });\n  });\n\n  describe('getPortService', () => {\n    it('should return correct service names for common ports', () => {\n      expect(getPortService(80)).toBe('HTTP');\n      expect(getPortService(443)).toBe('HTTPS');\n      expect(getPortService(22)).toBe('SSH');\n      expect(getPortService(3306)).toBe('MySQL');\n    });\n\n    it('should return \"Unknown\" for uncommon ports', () => {\n      expect(getPortService(12345)).toBe('Unknown');\n      expect(getPortService(54321)).toBe('Unknown');\n    });\n  });\n\n  describe('PORT_RANGES', () => {\n    it('should have correct port range definitions', () => {\n      expect(PORT_RANGES['well-known'].min).toBe(1);\n      expect(PORT_RANGES['well-known'].max).toBe(1023);\n      expect(PORT_RANGES['registered'].min).toBe(1024);\n      expect(PORT_RANGES['registered'].max).toBe(49151);\n      expect(PORT_RANGES['dynamic'].min).toBe(49152);\n      expect(PORT_RANGES['dynamic'].max).toBe(65535);\n    });\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/number/random-port-generator/service.ts",
    "content": "import { InitialValuesType, RandomPortResult, PortRange } from './types';\n\n// Standard port ranges according to IANA\nexport const PORT_RANGES: Record<string, PortRange> = {\n  'well-known': {\n    name: 'Well-Known Ports',\n    min: 1,\n    max: 1023,\n    description:\n      'System ports (1-1023) - Reserved for common services like HTTP, HTTPS, SSH, etc.'\n  },\n  registered: {\n    name: 'Registered Ports',\n    min: 1024,\n    max: 49151,\n    description:\n      'User ports (1024-49151) - Available for applications and services'\n  },\n  dynamic: {\n    name: 'Dynamic Ports',\n    min: 49152,\n    max: 65535,\n    description:\n      'Private ports (49152-65535) - Available for temporary or private use'\n  },\n  custom: {\n    name: 'Custom Range',\n    min: 1,\n    max: 65535,\n    description: 'Custom port range - Specify your own min and max values'\n  }\n};\n\n/**\n * Generate random network ports within a specified range\n */\nexport function generateRandomPorts(\n  options: InitialValuesType\n): RandomPortResult {\n  const { portRange, minPort, maxPort, count, allowDuplicates, sortResults } =\n    options;\n\n  // Get the appropriate port range\n  const range = PORT_RANGES[portRange];\n  const actualMin = portRange === 'custom' ? minPort : range.min;\n  const actualMax = portRange === 'custom' ? maxPort : range.max;\n\n  if (actualMin >= actualMax) {\n    throw new Error('Minimum port must be less than maximum port');\n  }\n\n  if (count <= 0) {\n    throw new Error('Count must be greater than 0');\n  }\n\n  if (actualMin < 1 || actualMax > 65535) {\n    throw new Error('Ports must be between 1 and 65535');\n  }\n\n  if (!allowDuplicates && count > actualMax - actualMin + 1) {\n    throw new Error(\n      'Cannot generate unique ports: count exceeds available range'\n    );\n  }\n\n  const ports: number[] = [];\n\n  if (allowDuplicates) {\n    // Generate random ports with possible duplicates\n    for (let i = 0; i < count; i++) {\n      const randomPort = generateRandomPort(actualMin, actualMax);\n      ports.push(randomPort);\n    }\n  } else {\n    // Generate unique random ports\n    const availablePorts = new Set<number>();\n\n    // Create a pool of available ports\n    for (let i = actualMin; i <= actualMax; i++) {\n      availablePorts.add(i);\n    }\n\n    const availableArray = Array.from(availablePorts);\n\n    // Shuffle the available ports\n    for (let i = availableArray.length - 1; i > 0; i--) {\n      const j = Math.floor(Math.random() * (i + 1));\n      [availableArray[i], availableArray[j]] = [\n        availableArray[j],\n        availableArray[i]\n      ];\n    }\n\n    // Take the first 'count' ports\n    for (let i = 0; i < Math.min(count, availableArray.length); i++) {\n      ports.push(availableArray[i]);\n    }\n  }\n\n  // Sort if requested\n  if (sortResults) {\n    ports.sort((a, b) => a - b);\n  }\n\n  return {\n    ports,\n    range: {\n      ...range,\n      min: actualMin,\n      max: actualMax\n    },\n    count,\n    hasDuplicates: !allowDuplicates && hasDuplicatesInArray(ports),\n    isSorted: sortResults\n  };\n}\n\n/**\n * Generate a single random port within the specified range\n */\nfunction generateRandomPort(min: number, max: number): number {\n  return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n/**\n * Check if an array has duplicate values\n */\nfunction hasDuplicatesInArray(arr: number[]): boolean {\n  const seen = new Set<number>();\n  for (const num of arr) {\n    if (seen.has(num)) {\n      return true;\n    }\n    seen.add(num);\n  }\n  return false;\n}\n\n/**\n * Format ports for display\n */\nexport function formatPorts(ports: number[], separator: string): string {\n  return ports.map((port) => port.toString()).join(separator);\n}\n\n/**\n * Validate input parameters\n */\nexport function validateInput(options: InitialValuesType): string | null {\n  const { portRange, minPort, maxPort, count } = options;\n\n  if (count <= 0) {\n    return 'Count must be greater than 0';\n  }\n\n  if (count > 1000) {\n    return 'Count cannot exceed 1,000';\n  }\n\n  if (portRange === 'custom') {\n    if (minPort >= maxPort) {\n      return 'Minimum port must be less than maximum port';\n    }\n\n    if (minPort < 1 || maxPort > 65535) {\n      return 'Ports must be between 1 and 65535';\n    }\n  }\n\n  return null;\n}\n\n/**\n * Get port range information\n */\nexport function getPortRangeInfo(portRange: string): PortRange {\n  return PORT_RANGES[portRange] || PORT_RANGES['custom'];\n}\n\n/**\n * Check if a port is commonly used\n */\nexport function isCommonPort(port: number): boolean {\n  const commonPorts = [\n    20, 21, 22, 23, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 5432, 6379, 8080\n  ];\n  return commonPorts.includes(port);\n}\n\n/**\n * Get port service information\n */\nexport function getPortService(port: number): string {\n  const portServices: Record<number, string> = {\n    20: 'FTP Data',\n    21: 'FTP Control',\n    22: 'SSH',\n    23: 'Telnet',\n    25: 'SMTP',\n    53: 'DNS',\n    80: 'HTTP',\n    110: 'POP3',\n    143: 'IMAP',\n    443: 'HTTPS',\n    993: 'IMAPS',\n    995: 'POP3S',\n    3306: 'MySQL',\n    5432: 'PostgreSQL',\n    6379: 'Redis',\n    8080: 'HTTP Alternative'\n  };\n\n  return portServices[port] || 'Unknown';\n}\n"
  },
  {
    "path": "src/pages/tools/number/random-port-generator/types.ts",
    "content": "export type InitialValuesType = {\n  portRange: 'well-known' | 'registered' | 'dynamic' | 'custom';\n  minPort: number;\n  maxPort: number;\n  count: number;\n  allowDuplicates: boolean;\n  sortResults: boolean;\n  separator: string;\n};\n\nexport type PortRange = {\n  name: string;\n  min: number;\n  max: number;\n  description: string;\n};\n\nexport type RandomPortResult = {\n  ports: number[];\n  range: PortRange;\n  count: number;\n  hasDuplicates: boolean;\n  isSorted: boolean;\n};\n"
  },
  {
    "path": "src/pages/tools/number/sum/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { compute, NumberExtractionType } from './service';\nimport RadioWithTextField from '@components/options/RadioWithTextField';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolContent from '@components/ToolContent';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  extractionType: 'smart' as NumberExtractionType,\n  separator: '\\\\n',\n  printRunningSum: false\n};\ntype InitialValuesType = typeof initialValues;\nconst extractionTypes: {\n  title: string;\n  description: string;\n  type: NumberExtractionType;\n  withTextField: boolean;\n  textValueAccessor?: keyof typeof initialValues;\n}[] = [\n  {\n    title: 'Smart sum',\n    description: 'Auto detect numbers in the input.',\n    type: 'smart',\n    withTextField: false\n  },\n  {\n    title: 'Number Delimiter',\n    type: 'delimiter',\n    description:\n      'Input SeparatorCustomize the number separator here. (By default a line break.)',\n    withTextField: true,\n    textValueAccessor: 'separator'\n  }\n];\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Sum of Ten Positive Numbers',\n    description:\n      'In this example, we calculate the sum of ten positive integers. These integers are listed as a column and their total sum equals 19494.',\n    sampleText: `0\n1\n20\n33\n400\n505\n660\n777\n8008\n9090`,\n    sampleResult: `19494`,\n    sampleOptions: {\n      extractionType: 'delimiter',\n      separator: '\\\\n',\n      printRunningSum: false\n    }\n  },\n  {\n    title: 'Count Trees in the Park',\n    description:\n      'This example reverses a column of twenty three-syllable nouns and prints all the words from the bottom to top. To separate the list items, it uses the \\n character as input item separator, which means that each item is on its own line..',\n    sampleText: `This year gardeners have planted 20 red maples, 35 sweetgum, 13 quaking aspen, and 7 white oaks in the central park of the city.`,\n    sampleResult: `75`,\n    sampleOptions: {\n      extractionType: 'smart',\n      separator: '\\\\n',\n      printRunningSum: false\n    }\n  },\n  {\n    title: 'Sum of Integers and Decimals',\n    description:\n      'In this example, we add together ninety different values – positive numbers, negative numbers, integers and decimal fractions. We set the input separator to a comma and after adding all of them together, we get 0 as output.',\n    sampleText: `1, 2, 3, 4, 5, 6, 7, 8, 9, -1.1, -2.1, -3.1, -4.1, -5.1, -6.1, -7.1, -8.1, -9.1, 10, 20, 30, 40, 50, 60, 70, 80, 90, -10.2, -20.2, -30.2, -40.2, -50.2, -60.2, -70.2, -80.2, -90.2, 100, 200, 300, 400, 500, 600, 700, 800, 900, -100.3, -200.3, -300.3, -400.3, -500.3, -600.3, -700.3, -800.3, -900.3, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, -1000.4, -2000.4, -3000.4, -4000.4, -5000.4, -6000.4, -7000.4, -8000.4, -9000.4, 10001, 20001, 30001, 40001, 50001, 60001, 70001, 80001, 90001, -10000, -20000, -30000, -40000, -50000, -60000, -70000, -80000, -90000`,\n    sampleResult: `0`,\n    sampleOptions: {\n      extractionType: 'delimiter',\n      separator: ', ',\n      printRunningSum: false\n    }\n  },\n  {\n    title: 'Running Sum of Numbers',\n    description:\n      'In this example, we calculate the sum of all ten digits and enable the option \"Print Running Sum\". We get the intermediate values of the sum in the process of addition. Thus, we have the following sequence in the output: 0, 1 (0 + 1), 3 (0 + 1 + 2), 6 (0 + 1 + 2 + 3), 10 (0 + 1 + 2 + 3 + 4), and so on.',\n    sampleText: `0\n1\n2\n3\n4\n5\n6\n7\n8\n9`,\n    sampleResult: `0\n1\n3\n6\n10\n15\n21\n28\n36\n45`,\n    sampleOptions: {\n      extractionType: 'delimiter',\n      separator: '\\\\n',\n      printRunningSum: true\n    }\n  }\n];\n\nexport default function SumNumbers({ title }: ToolComponentProps) {\n  const { t } = useTranslation('number');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('sum.numberExtraction'),\n      component: extractionTypes.map(\n        ({ title, description, type, withTextField, textValueAccessor }) =>\n          withTextField ? (\n            <RadioWithTextField\n              key={type}\n              checked={type === values.extractionType}\n              title={t(`sum.extractionTypes.${type}.title`)}\n              fieldName={'extractionType'}\n              description={t(`sum.extractionTypes.${type}.description`)}\n              value={\n                textValueAccessor ? values[textValueAccessor].toString() : ''\n              }\n              onRadioClick={() => updateField('extractionType', type)}\n              onTextChange={(val) =>\n                textValueAccessor ? updateField(textValueAccessor, val) : null\n              }\n            />\n          ) : (\n            <SimpleRadio\n              key={title}\n              onClick={() => updateField('extractionType', type)}\n              checked={values.extractionType === type}\n              description={t(`sum.extractionTypes.${type}.description`)}\n              title={t(`sum.extractionTypes.${type}.title`)}\n            />\n          )\n      )\n    },\n    {\n      title: t('sum.runningSum'),\n      component: (\n        <CheckboxWithDesc\n          title={t('sum.printRunningSum')}\n          description={t('sum.printRunningSumDescription')}\n          checked={values.printRunningSum}\n          onChange={(value) => updateField('printRunningSum', value)}\n        />\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput\n          title={t('sum.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('sum.resultTitle')} value={result} />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={(optionsValues, input) => {\n        const { extractionType, printRunningSum, separator } = optionsValues;\n        setResult(compute(input, extractionType, printRunningSum, separator));\n      }}\n      setInput={setInput}\n      toolInfo={{\n        title: t('sum.toolInfo.title'),\n        description: t('sum.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/number/sum/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('number', {\n  path: 'sum',\n  icon: 'material-symbols:add',\n\n  keywords: ['sum', 'add', 'calculate', 'total'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'number:sum.title',\n    description: 'number:sum.description',\n    shortDescription: 'number:sum.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/number/sum/service.ts",
    "content": "export type NumberExtractionType = 'smart' | 'delimiter';\n\nfunction getAllNumbers(text: string): number[] {\n  const regex = /\\d+/g;\n  const matches = text.match(regex);\n  return matches ? matches.map(Number) : [];\n}\n\nexport const compute = (\n  input: string,\n  extractionType: NumberExtractionType,\n  printRunningSum: boolean,\n  separator: string\n): string => {\n  let numbers: number[] = [];\n  if (extractionType === 'smart') {\n    numbers = getAllNumbers(input);\n  } else {\n    const parts = input.split(separator);\n    // Filter out and convert parts that are numbers\n    numbers = parts\n      .filter((part) => !isNaN(Number(part)) && part.trim() !== '')\n      .map(Number);\n  }\n  if (printRunningSum) {\n    let result: string = '';\n    let sum: number = 0;\n    for (const i of numbers) {\n      sum = sum + i;\n      result = result + sum + '\\n';\n    }\n    return result;\n  } else\n    return numbers\n      .reduce((previousValue, currentValue) => previousValue + currentValue, 0)\n      .toString();\n};\n"
  },
  {
    "path": "src/pages/tools/number/sum/sum.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { compute } from './service';\n\ndescribe('compute function', () => {\n  it('should correctly sum numbers in smart extraction mode', () => {\n    const input = 'The 2 cats have 4 and 7 kittens';\n    const result = compute(input, 'smart', false, ',');\n    expect(result).toBe('13');\n  });\n\n  it('should correctly sum numbers with custom delimiter', () => {\n    const input = '2,4,7';\n    const result = compute(input, 'delimiter', false, ',');\n    expect(result).toBe('13');\n  });\n\n  it('should return running sum in smart extraction mode', () => {\n    const input = 'The 2 cats have 4 and 7 kittens';\n    const result = compute(input, 'smart', true, ',');\n    expect(result).toBe('2\\n6\\n13\\n');\n  });\n\n  it('should return running sum with custom delimiter', () => {\n    const input = '2,4,7';\n    const result = compute(input, 'delimiter', true, ',');\n    expect(result).toBe('2\\n6\\n13\\n');\n  });\n\n  it('should handle empty input gracefully in smart mode', () => {\n    const input = '';\n    const result = compute(input, 'smart', false, ',');\n    expect(result).toBe('0');\n  });\n\n  it('should handle empty input gracefully in delimiter mode', () => {\n    const input = '';\n    const result = compute(input, 'delimiter', false, ',');\n    expect(result).toBe('0');\n  });\n\n  it('should handle input with no numbers in smart mode', () => {\n    const input = 'There are no numbers here';\n    const result = compute(input, 'smart', false, ',');\n    expect(result).toBe('0');\n  });\n\n  it('should handle input with no numbers in delimiter mode', () => {\n    const input = 'a,b,c';\n    const result = compute(input, 'delimiter', false, ',');\n    expect(result).toBe('0');\n  });\n\n  it('should ignore non-numeric parts in delimiter mode', () => {\n    const input = '2,a,4,b,7';\n    const result = compute(input, 'delimiter', false, ',');\n    expect(result).toBe('13');\n  });\n\n  it('should handle different separators', () => {\n    const input = '2;4;7';\n    const result = compute(input, 'delimiter', false, ';');\n    expect(result).toBe('13');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/compress-pdf/index.tsx",
    "content": "import { Box, Typography } from '@mui/material';\nimport React, { useState, useEffect, useContext } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { compressPdf } from './service';\nimport { InitialValuesType, CompressionLevel } from './types';\nimport ToolPdfInput from '@components/input/ToolPdfInput';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { PDFDocument } from 'pdf-lib';\nimport { CustomSnackBarContext } from '../../../../contexts/CustomSnackBarContext';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  compressionLevel: 'low'\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Low Compression',\n    description: 'Minimal quality loss with slight file size reduction',\n    sampleText: '',\n    sampleResult: '',\n    sampleOptions: {\n      compressionLevel: 'low'\n    }\n  },\n  {\n    title: 'Medium Compression',\n    description: 'Balance between file size and quality',\n    sampleText: '',\n    sampleResult: '',\n    sampleOptions: {\n      compressionLevel: 'medium'\n    }\n  },\n  {\n    title: 'High Compression',\n    description: 'Maximum file size reduction with some quality loss',\n    sampleText: '',\n    sampleResult: '',\n    sampleOptions: {\n      compressionLevel: 'high'\n    }\n  }\n];\n\nexport default function CompressPdf({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('pdf');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [resultSize, setResultSize] = useState<string>('');\n  const [isProcessing, setIsProcessing] = useState<boolean>(false);\n  const [fileInfo, setFileInfo] = useState<{\n    size: string;\n    pages: number;\n  } | null>(null);\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n\n  // Get the PDF info when a file is uploaded\n  useEffect(() => {\n    const getPdfInfo = async () => {\n      if (!input) {\n        setFileInfo(null);\n        return;\n      }\n\n      try {\n        const arrayBuffer = await input.arrayBuffer();\n        const pdf = await PDFDocument.load(arrayBuffer);\n        const pages = pdf.getPageCount();\n        const size = formatFileSize(input.size);\n\n        setFileInfo({ size, pages });\n      } catch (error) {\n        console.error('Error getting PDF info:', error);\n        setFileInfo(null);\n        showSnackBar(t('compressPdf.errorReadingPdf'), 'error');\n      }\n    };\n\n    getPdfInfo();\n  }, [input]);\n\n  const formatFileSize = (bytes: number): string => {\n    if (bytes === 0) return '0 Bytes';\n\n    const k = 1024;\n    const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n    const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n  };\n\n  const compute = async (values: InitialValuesType, input: File | null) => {\n    if (!input) return;\n\n    try {\n      setIsProcessing(true);\n      const compressedPdf = await compressPdf(input, values);\n      setResult(compressedPdf);\n\n      // Log compression results\n      const compressionRatio = (compressedPdf.size / input.size) * 100;\n      console.log(`Compression Ratio: ${compressionRatio.toFixed(2)}%`);\n      setResultSize(formatFileSize(compressedPdf.size));\n    } catch (error) {\n      console.error('Error compressing PDF:', error);\n      showSnackBar(\n        t('compressPdf.errorCompressingPdf', {\n          error: error instanceof Error ? error.message : String(error)\n        }),\n        'error'\n      );\n      setResult(null);\n    } finally {\n      setIsProcessing(false);\n    }\n  };\n\n  const compressionOptions: {\n    value: CompressionLevel;\n    label: string;\n    description: string;\n  }[] = [\n    {\n      value: 'low',\n      label: t('compressPdf.lowCompression'),\n      description: t('compressPdf.lowCompressionDescription')\n    },\n    {\n      value: 'medium',\n      label: t('compressPdf.mediumCompression'),\n      description: t('compressPdf.mediumCompressionDescription')\n    },\n    {\n      value: 'high',\n      label: t('compressPdf.highCompression'),\n      description: t('compressPdf.highCompressionDescription')\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={initialValues}\n      compute={compute}\n      inputComponent={\n        <ToolPdfInput\n          value={input}\n          onChange={setInput}\n          accept={['application/pdf']}\n          title={t('compressPdf.inputTitle')}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          title={t('compressPdf.resultTitle')}\n          value={result}\n          extension={'pdf'}\n          loading={isProcessing}\n          loadingText={t('compressPdf.compressingPdf')}\n        />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('compressPdf.compressionSettings'),\n          component: (\n            <Box>\n              <Box>\n                <Typography variant=\"subtitle2\" sx={{ mb: 1 }}>\n                  {t('compressPdf.compressionLevel')}\n                </Typography>\n\n                {compressionOptions.map((option) => (\n                  <SimpleRadio\n                    key={option.value}\n                    title={option.label}\n                    description={option.description}\n                    checked={values.compressionLevel === option.value}\n                    onClick={() => {\n                      updateField('compressionLevel', option.value);\n                    }}\n                  />\n                ))}\n              </Box>\n              {fileInfo && (\n                <Box\n                  sx={{\n                    mt: 2,\n                    p: 2,\n                    bgcolor: 'background.paper',\n                    borderRadius: 1\n                  }}\n                >\n                  <Typography variant=\"body2\">\n                    {t('compressPdf.fileSize')}:{' '}\n                    <strong>{fileInfo.size}</strong>\n                  </Typography>\n                  <Typography variant=\"body2\">\n                    {t('compressPdf.pages')}: <strong>{fileInfo.pages}</strong>\n                  </Typography>\n                  {resultSize && (\n                    <Typography variant=\"body2\">\n                      {t('compressPdf.compressedFileSize')}:{' '}\n                      <strong>{resultSize}</strong>\n                    </Typography>\n                  )}\n                </Box>\n              )}\n            </Box>\n          )\n        }\n      ]}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/compress-pdf/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('pdf', {\n  path: 'compress-pdf',\n  icon: 'material-symbols:compress',\n\n  keywords: [\n    'reduce',\n    'size',\n    'optimize',\n    'shrink',\n    'file',\n    'ghostscript',\n    'secure',\n    'private',\n    'browser',\n    'webassembly'\n  ],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'pdf:compressPdf.title',\n    description: 'pdf:compressPdf.description',\n    shortDescription: 'pdf:compressPdf.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/compress-pdf/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { compressWithGhostScript } from '../../../../lib/ghostscript/worker-init';\nimport { loadPDFData } from '../utils';\n\n/**\n * Compresses a PDF file using either Ghostscript WASM (preferred)\n * or falls back to pdf-lib if WASM fails\n *\n * @param pdfFile - The PDF file to compress\n * @param options - Compression options including compression level\n * @returns A Promise that resolves to a compressed PDF File\n */\nexport async function compressPdf(\n  pdfFile: File,\n  options: InitialValuesType\n): Promise<File> {\n  // Check if file is a PDF\n  if (pdfFile.type !== 'application/pdf') {\n    throw new Error('The provided file is not a PDF');\n  }\n\n  const dataObject = {\n    psDataURL: URL.createObjectURL(pdfFile),\n    compressionLevel: options.compressionLevel\n  };\n  const compressedFileUrl: string = await compressWithGhostScript(dataObject);\n  return await loadPDFData(compressedFileUrl, pdfFile.name);\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/compress-pdf/types.ts",
    "content": "export type CompressionLevel = 'low' | 'medium' | 'high';\n\nexport type InitialValuesType = {\n  compressionLevel: CompressionLevel;\n};\n"
  },
  {
    "path": "src/pages/tools/pdf/convert-to-pdf/convert-to-pdf.service.test.tsx",
    "content": "import { render, screen, fireEvent } from '@testing-library/react';\nimport ConvertToPdf from './index';\nimport { vi } from 'vitest';\nimport '@testing-library/jest-dom';\n\ndescribe('ConvertToPdf', () => {\n  it('renders with default state values (full, portrait hidden, no scale shown)', () => {\n    render(<ConvertToPdf title=\"Test PDF\" />);\n\n    expect(screen.getByLabelText(/Full Size \\(Same as Image\\)/i)).toBeChecked();\n\n    expect(screen.queryByLabelText(/A4 Page/i)).toBeInTheDocument();\n    expect(screen.queryByLabelText(/Portrait/i)).not.toBeInTheDocument();\n    expect(screen.queryByText(/Scale image:/i)).not.toBeInTheDocument();\n  });\n\n  it('switches to A4 page type and shows orientation and scale', () => {\n    render(<ConvertToPdf title=\"Test PDF\" />);\n\n    const a4Option = screen.getByLabelText(/A4 Page/i);\n    fireEvent.click(a4Option);\n    expect(a4Option).toBeChecked();\n\n    expect(screen.getByLabelText(/Portrait/i)).toBeChecked();\n    expect(screen.getByText(/Scale image:\\s*100%/i)).toBeInTheDocument();\n  });\n\n  it('updates scale when slider moves (after switching to A4)', () => {\n    render(<ConvertToPdf title=\"Test PDF\" />);\n\n    fireEvent.click(screen.getByLabelText(/A4 Page/i));\n\n    const slider = screen.getByRole('slider');\n    fireEvent.change(slider, { target: { value: 80 } });\n\n    expect(screen.getByText(/Scale image:\\s*80%/i)).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/convert-to-pdf/index.tsx",
    "content": "import {\n  Box,\n  Slider,\n  Typography,\n  RadioGroup,\n  FormControlLabel,\n  Radio,\n  Stack\n} from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolImageInput from 'components/input/ToolImageInput';\nimport ToolFileResult from 'components/result/ToolFileResult';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { FormValues, Orientation, PageType, initialValues } from './types';\nimport { buildPdf } from './service';\n\nconst initialFormValues: FormValues = initialValues;\n\nexport default function ConvertToPdf({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [imageSize, setImageSize] = useState<{\n    widthMm: number;\n    heightMm: number;\n    widthPx: number;\n    heightPx: number;\n  } | null>(null);\n\n  const compute = async (values: FormValues) => {\n    if (!input) return;\n    const { pdfFile, imageSize } = await buildPdf({\n      file: input,\n      pageType: values.pageType,\n      orientation: values.orientation,\n      scale: values.scale\n    });\n    setResult(pdfFile);\n    setImageSize(imageSize);\n  };\n\n  return (\n    <ToolContent<FormValues, File | null>\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={initialFormValues}\n      compute={compute}\n      inputComponent={\n        <Box>\n          <ToolImageInput\n            value={input}\n            onChange={setInput}\n            accept={[\n              'image/png',\n              'image/jpeg',\n              'image/webp',\n              'image/tiff',\n              'image/gif',\n              'image/heic',\n              'image/heif',\n              'image/x-adobe-dng',\n              'image/x-canon-cr2',\n              'image/x-nikon-nef',\n              'image/x-sony-arw',\n              'image/vnd.adobe.photoshop'\n            ]}\n            title=\"Input Image\"\n          />\n        </Box>\n      }\n      getGroups={({ values, updateField }) => {\n        return [\n          {\n            title: '',\n            component: (\n              <Stack spacing={4}>\n                <Box>\n                  <Typography variant=\"h6\">PDF Type</Typography>\n                  <RadioGroup\n                    row\n                    value={values.pageType}\n                    onChange={(e) =>\n                      updateField('pageType', e.target.value as PageType)\n                    }\n                  >\n                    <FormControlLabel\n                      value=\"full\"\n                      control={<Radio />}\n                      label=\"Full Size (Same as Image)\"\n                    />\n                    <FormControlLabel\n                      value=\"a4\"\n                      control={<Radio />}\n                      label=\"A4 Page\"\n                    />\n                  </RadioGroup>\n\n                  {values.pageType === 'full' && imageSize && (\n                    <Typography variant=\"body2\" color=\"text.secondary\">\n                      Image size: {imageSize.widthMm.toFixed(1)} ×{' '}\n                      {imageSize.heightMm.toFixed(1)} mm ({imageSize.widthPx} ×{' '}\n                      {imageSize.heightPx} px)\n                    </Typography>\n                  )}\n                </Box>\n\n                {values.pageType === 'a4' && (\n                  <Box>\n                    <Typography variant=\"h6\">Orientation</Typography>\n                    <RadioGroup\n                      row\n                      value={values.orientation}\n                      onChange={(e) =>\n                        updateField(\n                          'orientation',\n                          e.target.value as Orientation\n                        )\n                      }\n                    >\n                      <FormControlLabel\n                        value=\"portrait\"\n                        control={<Radio />}\n                        label=\"Portrait (Vertical)\"\n                      />\n                      <FormControlLabel\n                        value=\"landscape\"\n                        control={<Radio />}\n                        label=\"Landscape (Horizontal)\"\n                      />\n                    </RadioGroup>\n                  </Box>\n                )}\n\n                {values.pageType === 'a4' && (\n                  <Box>\n                    <Typography variant=\"h6\">Scale</Typography>\n                    <Typography>Scale image: {values.scale}%</Typography>\n                    <Slider\n                      value={values.scale}\n                      onChange={(_, v) => updateField('scale', v as number)}\n                      min={10}\n                      max={100}\n                      step={1}\n                      valueLabelDisplay=\"auto\"\n                    />\n                  </Box>\n                )}\n              </Stack>\n            )\n          }\n        ] as const;\n      }}\n      resultComponent={\n        <ToolFileResult title=\"Output PDF\" value={result} extension=\"pdf\" />\n      }\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/convert-to-pdf/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('pdf', {\n  i18n: {\n    name: 'pdf:convertToPdf.title',\n    description: 'pdf:convertToPdf.description',\n    shortDescription: 'pdf:convertToPdf.shortDescription'\n  },\n\n  path: 'convert-to-pdf',\n  icon: 'ph:file-pdf-thin',\n\n  keywords: [\n    'convert',\n    'pdf',\n    'image',\n    'jpg',\n    'jpeg',\n    'png',\n    'gif',\n    'tiff',\n    'webp',\n    'heic',\n    'raw',\n    'psd',\n    'svg',\n    'quality',\n    'compression'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/convert-to-pdf/service.ts",
    "content": "import jsPDF from 'jspdf';\nimport { Orientation, PageType, ImageSize } from './types';\n\nexport interface ComputeOptions {\n  file: File;\n  pageType: PageType;\n  orientation: Orientation;\n  scale: number; // 10..100 (only applied for A4)\n}\n\nexport interface ComputeResult {\n  pdfFile: File;\n  imageSize: ImageSize;\n}\n\nexport async function buildPdf({\n  file,\n  pageType,\n  orientation,\n  scale\n}: ComputeOptions): Promise<ComputeResult> {\n  const img = new Image();\n  img.src = URL.createObjectURL(file);\n\n  try {\n    await img.decode();\n\n    const pxToMm = (px: number) => px * 0.264583;\n    const imgWidthMm = pxToMm(img.width);\n    const imgHeightMm = pxToMm(img.height);\n\n    const pdf =\n      pageType === 'full'\n        ? new jsPDF({\n            orientation: imgWidthMm > imgHeightMm ? 'landscape' : 'portrait',\n            unit: 'mm',\n            format: [imgWidthMm, imgHeightMm]\n          })\n        : new jsPDF({\n            orientation,\n            unit: 'mm',\n            format: 'a4'\n          });\n\n    pdf.setDisplayMode('fullwidth');\n\n    const pageWidth = pdf.internal.pageSize.getWidth();\n    const pageHeight = pdf.internal.pageSize.getHeight();\n\n    const widthRatio = pageWidth / img.width;\n    const heightRatio = pageHeight / img.height;\n    const fitScale = Math.min(widthRatio, heightRatio);\n\n    const finalWidth =\n      pageType === 'full' ? pageWidth : img.width * fitScale * (scale / 100);\n\n    const finalHeight =\n      pageType === 'full' ? pageHeight : img.height * fitScale * (scale / 100);\n\n    const x = pageType === 'full' ? 0 : (pageWidth - finalWidth) / 2;\n    const y = pageType === 'full' ? 0 : (pageHeight - finalHeight) / 2;\n\n    pdf.addImage(img, 'JPEG', x, y, finalWidth, finalHeight);\n\n    const blob = pdf.output('blob');\n    const fileName = file.name.replace(/\\.[^/.]+$/, '') + '.pdf';\n\n    return {\n      pdfFile: new File([blob], fileName, { type: 'application/pdf' }),\n      imageSize: {\n        widthMm: imgWidthMm,\n        heightMm: imgHeightMm,\n        widthPx: img.width,\n        heightPx: img.height\n      }\n    };\n  } finally {\n    URL.revokeObjectURL(img.src);\n  }\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/convert-to-pdf/types.ts",
    "content": "export type Orientation = 'portrait' | 'landscape';\nexport type PageType = 'a4' | 'full';\n\nexport interface ImageSize {\n  widthMm: number;\n  heightMm: number;\n  widthPx: number;\n  heightPx: number;\n}\n\nexport interface FormValues {\n  pageType: PageType;\n  orientation: Orientation;\n  scale: number;\n}\n\nexport const initialValues: FormValues = {\n  pageType: 'full',\n  orientation: 'portrait',\n  scale: 100\n};\n"
  },
  {
    "path": "src/pages/tools/pdf/editor/index.tsx",
    "content": "import React from 'react';\nimport { Box } from '@mui/material';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { EmbedPDF } from '@simplepdf/react-embed-pdf';\n\nexport default function PdfEditor({ title }: ToolComponentProps) {\n  return (\n    <ToolContent\n      title={title}\n      initialValues={{}}\n      getGroups={null}\n      input={null}\n      inputComponent={\n        <Box sx={{ width: '100%', height: '80vh' }}>\n          <EmbedPDF mode=\"inline\" style={{ width: '100%', height: '100%' }} />\n        </Box>\n      }\n      toolInfo={{\n        title: 'PDF Editor',\n        description:\n          'Edit, annotate, highlight, fill forms, and export your PDFs entirely in the browser. Add text, drawings, signatures, and more to your PDF documents with this powerful online editor.'\n      }}\n      compute={() => {\n        /* no background compute required */\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/editor/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('pdf', {\n  i18n: {\n    name: 'pdf:editor.title',\n    description: 'pdf:editor.description',\n    shortDescription: 'pdf:editor.shortDescription',\n    userTypes: ['generalUsers']\n  },\n\n  path: 'editor',\n  icon: 'mdi:file-document-edit',\n\n  keywords: [\n    'pdf',\n    'editor',\n    'edit',\n    'annotate',\n    'highlight',\n    'form',\n    'fill',\n    'text',\n    'drawing',\n    'signature',\n    'export',\n    'annotation',\n    'markup'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/index.ts",
    "content": "import { tool as pdfPdfToPng } from './pdf-to-png/meta';\nimport { tool as pdfRotatePdf } from './rotate-pdf/meta';\nimport { meta as splitPdfMeta } from './split-pdf/meta';\nimport { meta as mergePdf } from './merge-pdf/meta';\nimport { DefinedTool } from '@tools/defineTool';\nimport { tool as compressPdfTool } from './compress-pdf/meta';\nimport { tool as protectPdfTool } from './protect-pdf/meta';\nimport { meta as pdfToEpub } from './pdf-to-epub/meta';\nimport { tool as pdfEditor } from './editor/meta';\nimport { tool as convertToPdf } from './convert-to-pdf/meta';\n\nexport const pdfTools: DefinedTool[] = [\n  pdfEditor,\n  splitPdfMeta,\n  pdfRotatePdf,\n  compressPdfTool,\n  protectPdfTool,\n  mergePdf,\n  pdfToEpub,\n  pdfPdfToPng,\n  convertToPdf\n];\n"
  },
  {
    "path": "src/pages/tools/pdf/merge-pdf/index.tsx",
    "content": "import { useState } from 'react';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { mergePdf } from './service';\nimport ToolMultiPdfInput, {\n  MultiPdfInput\n} from '@components/input/ToolMultiplePdfInput';\nimport { useTranslation } from 'react-i18next';\n\nexport default function MergePdf({ title }: ToolComponentProps) {\n  const { t } = useTranslation('pdf');\n  const [input, setInput] = useState<MultiPdfInput[]>([]);\n  const [result, setResult] = useState<File | null>(null);\n  const [isProcessing, setIsProcessing] = useState<boolean>(false);\n\n  const compute = async (values: File[], input: MultiPdfInput[]) => {\n    if (input.length === 0) {\n      return;\n    }\n\n    try {\n      setIsProcessing(true);\n      const mergeResult = await mergePdf(input.map((i) => i.file));\n      setResult(mergeResult);\n    } catch (error) {\n      throw new Error('Error merging PDF:' + error);\n    } finally {\n      setIsProcessing(false);\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={input.map((i) => i.file)}\n      compute={compute}\n      inputComponent={\n        <ToolMultiPdfInput\n          value={input}\n          onChange={(pdfInputs) => {\n            setInput(pdfInputs);\n          }}\n          accept={['application/pdf']}\n          title={t('merge.inputTitle')}\n          type=\"pdf\"\n        />\n      }\n      getGroups={null}\n      resultComponent={\n        <ToolFileResult\n          title={t('merge.resultTitle')}\n          value={result}\n          extension={'pdf'}\n          loading={isProcessing}\n          loadingText={t('merge.loadingText')}\n        />\n      }\n      toolInfo={{\n        title: t('merge.toolInfo.title'),\n        description: t('merge.toolInfo.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/merge-pdf/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const meta = defineTool('pdf', {\n  icon: 'material-symbols-light:merge',\n  component: lazy(() => import('./index')),\n  keywords: ['pages', 'combine', 'document', 'join', 'append'],\n  path: 'merge-pdf',\n  i18n: {\n    name: 'pdf:mergePdf.title',\n    description: 'pdf:mergePdf.description',\n    shortDescription: 'pdf:mergePdf.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/merge-pdf/service.test.ts",
    "content": "import { parsePageRanges } from './service';\n\ndescribe('parsePageRanges', () => {\n  test('should return all pages when input is empty', () => {\n    expect(parsePageRanges('', 5)).toEqual([1, 2, 3, 4, 5]);\n  });\n\n  test('should parse single page numbers', () => {\n    expect(parsePageRanges('1,3,5', 5)).toEqual([1, 3, 5]);\n  });\n\n  test('should parse page ranges', () => {\n    expect(parsePageRanges('2-4', 5)).toEqual([2, 3, 4]);\n  });\n\n  test('should parse mixed page numbers and ranges', () => {\n    expect(parsePageRanges('1,3-5', 5)).toEqual([1, 3, 4, 5]);\n  });\n\n  test('should handle whitespace', () => {\n    expect(parsePageRanges(' 1, 3 - 5 ', 5)).toEqual([1, 3, 4, 5]);\n  });\n\n  test('should ignore invalid page numbers', () => {\n    expect(parsePageRanges('1,a,3', 5)).toEqual([1, 3]);\n  });\n\n  test('should ignore out-of-range page numbers', () => {\n    expect(parsePageRanges('1,6,3', 5)).toEqual([1, 3]);\n  });\n\n  test('should limit ranges to valid pages', () => {\n    expect(parsePageRanges('0-6', 5)).toEqual([1, 2, 3, 4, 5]);\n  });\n\n  test('should handle reversed ranges', () => {\n    expect(parsePageRanges('4-2', 5)).toEqual([2, 3, 4]);\n  });\n\n  test('should remove duplicates', () => {\n    expect(parsePageRanges('1,1,2,2-4,3', 5)).toEqual([1, 2, 3, 4]);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/merge-pdf/service.ts",
    "content": "import { PDFDocument } from 'pdf-lib';\n\n/**\n * Parses a page range string and returns an array of page numbers\n * @param pageRangeStr String like \"1,3-5,7\" to extract pages 1, 3, 4, 5, and 7\n * @param totalPages Total number of pages in the PDF\n * @returns Array of page numbers to extract\n */\nexport function parsePageRanges(\n  pageRangeStr: string,\n  totalPages: number\n): number[] {\n  if (!pageRangeStr.trim()) {\n    return Array.from({ length: totalPages }, (_, i) => i + 1);\n  }\n\n  const pageNumbers = new Set<number>();\n  const ranges = pageRangeStr.split(',');\n\n  for (const range of ranges) {\n    const trimmedRange = range.trim();\n\n    if (trimmedRange.includes('-')) {\n      const [start, end] = trimmedRange.split('-').map(Number);\n      if (!isNaN(start) && !isNaN(end)) {\n        // Handle both forward and reversed ranges\n        const normalizedStart = Math.min(start, end);\n        const normalizedEnd = Math.max(start, end);\n\n        for (\n          let i = Math.max(1, normalizedStart);\n          i <= Math.min(totalPages, normalizedEnd);\n          i++\n        ) {\n          pageNumbers.add(i);\n        }\n      }\n    } else {\n      const pageNum = parseInt(trimmedRange, 10);\n      if (!isNaN(pageNum) && pageNum >= 1 && pageNum <= totalPages) {\n        pageNumbers.add(pageNum);\n      }\n    }\n  }\n\n  return [...pageNumbers].sort((a, b) => a - b);\n}\n\n/**\n * Splits a PDF file based on specified page ranges\n * @param pdfFile The input PDF file\n * @param pageRanges String specifying which pages to extract (e.g., \"1,3-5,7\")\n * @returns Promise resolving to a new PDF file with only the selected pages\n */\nexport async function splitPdf(\n  pdfFile: File,\n  pageRanges: string\n): Promise<File> {\n  const arrayBuffer = await pdfFile.arrayBuffer();\n  const sourcePdf = await PDFDocument.load(arrayBuffer);\n  const totalPages = sourcePdf.getPageCount();\n  const pagesToExtract = parsePageRanges(pageRanges, totalPages);\n\n  const newPdf = await PDFDocument.create();\n  const copiedPages = await newPdf.copyPages(\n    sourcePdf,\n    pagesToExtract.map((pageNum) => pageNum - 1)\n  );\n  copiedPages.forEach((page) => newPdf.addPage(page));\n\n  const newPdfBytes = await newPdf.save();\n  const newFileName = pdfFile.name.replace('.pdf', '-extracted.pdf');\n  return new File([newPdfBytes as any], newFileName, {\n    type: 'application/pdf'\n  });\n}\n\n/**\n * Merges multiple PDF files into a single document\n * @param pdfFiles Array of PDF files to merge\n * @returns Promise resolving to a new PDF file with all pages combined\n */\nexport async function mergePdf(pdfFiles: File[]): Promise<File> {\n  const mergedPdf = await PDFDocument.create();\n  for (const pdfFile of pdfFiles) {\n    const arrayBuffer = await pdfFile.arrayBuffer();\n    const pdf = await PDFDocument.load(arrayBuffer);\n    const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());\n    copiedPages.forEach((page) => mergedPdf.addPage(page));\n  }\n\n  const mergedPdfBytes = await mergedPdf.save();\n  const mergedFileName = 'merged.pdf';\n  return new File([mergedPdfBytes as any], mergedFileName, {\n    type: 'application/pdf'\n  });\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/pdf-to-epub/index.tsx",
    "content": "import { useState, useEffect } from 'react';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolPdfInput from '@components/input/ToolPdfInput';\nimport { convertPdfToEpub } from './service';\n\nexport default function PdfToEpub({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [isProcessing, setIsProcessing] = useState<boolean>(false);\n\n  const compute = async (options: {}, input: File | null) => {\n    if (!input) return;\n    try {\n      setIsProcessing(true);\n      setResult(null);\n      const epub = await convertPdfToEpub(input);\n      setResult(epub);\n    } catch (error) {\n      console.error('Failed to convert PDF to EPUB:', error);\n    } finally {\n      setIsProcessing(false);\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={{}}\n      compute={compute}\n      inputComponent={\n        <ToolPdfInput\n          value={input}\n          onChange={(file) => setInput(file)}\n          accept={['application/pdf']}\n          title={'Input PDF'}\n        />\n      }\n      getGroups={null}\n      resultComponent={\n        <ToolFileResult\n          title={'EPUB Output'}\n          value={result}\n          extension={'epub'}\n          loading={isProcessing}\n          loadingText={'Converting PDF to EPUB...'}\n        />\n      }\n      toolInfo={{\n        title: 'How to Use PDF to EPUB?',\n        description: `Upload a PDF file and this tool will convert it into an EPUB format, suitable for most e-reader devices.`\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/pdf-to-epub/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const meta = defineTool('pdf', {\n  icon: 'material-symbols:import-contacts',\n  component: lazy(() => import('./index')),\n  keywords: ['pdf', 'epub', 'convert', 'ebook'],\n  path: 'pdf-to-epub',\n  i18n: {\n    name: 'pdf:pdfToEpub.title',\n    description: 'pdf:pdfToEpub.description',\n    shortDescription: 'pdf:pdfToEpub.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/pdf-to-epub/service.ts",
    "content": "import * as pdfjsLib from 'pdfjs-dist';\nimport pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min?url';\nimport JSZip from 'jszip';\n\n// Set worker source for PDF.js\npdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;\n\nfunction formatTextToParagraphs(raw: string): string {\n  return raw\n    .split(/\\n{2,}|\\r{2,}/g) // Split on double line breaks\n    .map((p) => p.trim())\n    .filter((p) => p.length > 0)\n    .map((p) => `<p>${p.replace(/\\n/g, ' ')}</p>`)\n    .join('\\n');\n}\n\nexport async function convertPdfToEpub(pdfFile: File): Promise<File> {\n  const arrayBuffer = await pdfFile.arrayBuffer();\n\n  const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer });\n  const pdfDoc = await loadingTask.promise;\n  const numPages = pdfDoc.numPages;\n\n  // Extracting text\n  const pages: string[] = [];\n  for (let i = 1; i <= numPages; i++) {\n    const page = await pdfDoc.getPage(i);\n    const textContent = await page.getTextContent();\n\n    const pageText = textContent.items.map((item: any) => item.str).join('\\n'); // Preserve line breaks better\n\n    pages.push(pageText);\n  }\n\n  const zip = new JSZip();\n\n  zip.file('mimetype', 'application/epub+zip', { compression: 'STORE' });\n\n  const metaInf = zip.folder('META-INF');\n  metaInf!.file(\n    'container.xml',\n    `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n  <rootfiles>\n    <rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/>\n  </rootfiles>\n</container>`\n  );\n\n  const oebps = zip.folder('OEBPS');\n  const bookTitle = pdfFile.name.replace(/\\.pdf$/i, '');\n\n  const contentOpf = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<package xmlns=\"http://www.idpf.org/2007/opf\" unique-identifier=\"bookid\" version=\"2.0\">\n  <metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n    <dc:title>${bookTitle}</dc:title>\n    <dc:creator>Converted by omni-tools</dc:creator>\n    <dc:identifier id=\"bookid\">${Date.now()}</dc:identifier>\n    <dc:language>en</dc:language>\n  </metadata>\n  <manifest>\n    <item id=\"ncx\" href=\"toc.ncx\" media-type=\"application/x-dtbncx+xml\"/>\n    ${pages\n      .map(\n        (_, index) =>\n          `<item id=\"chapter${index + 1}\" href=\"chapter${\n            index + 1\n          }.xhtml\" media-type=\"application/xhtml+xml\"/>`\n      )\n      .join('\\n    ')}\n  </manifest>\n  <spine toc=\"ncx\">\n    ${pages\n      .map((_, index) => `<itemref idref=\"chapter${index + 1}\"/>`)\n      .join('\\n    ')}\n  </spine>\n</package>`;\n\n  oebps!.file('content.opf', contentOpf);\n\n  const tocNcx = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" version=\"2005-1\">\n  <head>\n    <meta name=\"dtb:uid\" content=\"${Date.now()}\"/>\n    <meta name=\"dtb:depth\" content=\"1\"/>\n    <meta name=\"dtb:totalPageCount\" content=\"0\"/>\n    <meta name=\"dtb:maxPageNumber\" content=\"0\"/>\n  </head>\n  <docTitle>\n    <text>${bookTitle}</text>\n  </docTitle>\n  <navMap>\n    ${pages\n      .map(\n        (_, index) =>\n          `<navPoint id=\"navpoint-${index + 1}\" playOrder=\"${index + 1}\">\n        <navLabel>\n          <text>Page ${index + 1}</text>\n        </navLabel>\n        <content src=\"chapter${index + 1}.xhtml\"/>\n      </navPoint>`\n      )\n      .join('\\n    ')}\n  </navMap>\n</ncx>`;\n\n  oebps!.file('toc.ncx', tocNcx);\n\n  pages.forEach((pageText, index) => {\n    const formattedBody = formatTextToParagraphs(pageText);\n\n    const chapterXhtml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n  <title>Page ${index + 1}</title>\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n  <style>\n    body {\n      font-family: serif;\n      line-height: 1.6;\n      margin: 1em;\n    }\n    p {\n      margin-bottom: 1em;\n      text-align: justify;\n    }\n  </style>\n</head>\n<body>\n  <h1>Page ${index + 1}</h1>\n  ${formattedBody}\n</body>\n</html>`;\n\n    oebps!.file(`chapter${index + 1}.xhtml`, chapterXhtml);\n  });\n\n  const epubBuffer = await zip.generateAsync({ type: 'arraybuffer' });\n\n  return new File([epubBuffer], pdfFile.name.replace(/\\.pdf$/i, '.epub'), {\n    type: 'application/epub+zip'\n  });\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/pdf-to-png/index.tsx",
    "content": "import { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolPdfInput from '@components/input/ToolPdfInput';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { convertPdfToPngImages } from './service';\nimport ToolMultiFileResult from '@components/result/ToolMultiFileResult';\n\ntype ImagePreview = {\n  blob: Blob;\n  url: string;\n  filename: string;\n};\n\nexport default function PdfToPng({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [images, setImages] = useState<ImagePreview[]>([]);\n  const [zipBlob, setZipBlob] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const compute = async (_: {}, file: File | null) => {\n    if (!file) return;\n    setLoading(true);\n    setImages([]);\n    setZipBlob(null);\n    try {\n      const { images, zipFile } = await convertPdfToPngImages(file);\n      setImages(images);\n      setZipBlob(zipFile);\n    } catch (err) {\n      console.error('Conversion failed:', err);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={{}}\n      compute={compute}\n      inputComponent={\n        <ToolPdfInput\n          value={input}\n          onChange={setInput}\n          accept={['application/pdf']}\n          title=\"Upload a PDF\"\n        />\n      }\n      resultComponent={\n        <ToolMultiFileResult\n          title=\"Converted PNG Pages\"\n          value={images.map((img) => {\n            return new File([img.blob], img.filename, { type: 'image/png' });\n          })}\n          zipFile={zipBlob}\n          loading={loading}\n          loadingText=\"Converting PDF pages\"\n        />\n      }\n      getGroups={null}\n      toolInfo={{\n        title: 'Convert PDF pages into PNG images',\n        description:\n          'Upload your PDF and get each page rendered as a high-quality PNG. You can preview, download individually, or get all images in a ZIP.'\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/pdf-to-png/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('pdf', {\n  i18n: {\n    name: 'pdf:pdfToPng.title',\n    description: 'pdf:pdfToPng.description',\n    shortDescription: 'pdf:pdfToPng.shortDescription',\n    longDescription: 'pdf:pdfToPng.longDescription',\n    userTypes: ['generalUsers']\n  },\n\n  path: 'pdf-to-png',\n  icon: 'mdi:image-multiple', // Iconify icon ID\n\n  keywords: ['pdf', 'png', 'convert', 'image', 'extract', 'pages'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/pdf-to-png/service.ts",
    "content": "import * as pdfjsLib from 'pdfjs-dist';\nimport pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min?url';\nimport JSZip from 'jszip';\n\npdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;\n\ntype ImagePreview = {\n  blob: Blob;\n  url: string;\n  filename: string;\n};\n\nexport async function convertPdfToPngImages(pdfFile: File): Promise<{\n  images: ImagePreview[];\n  zipFile: File;\n}> {\n  const arrayBuffer = await pdfFile.arrayBuffer();\n  const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;\n  const zip = new JSZip();\n  const images: ImagePreview[] = [];\n\n  for (let i = 1; i <= pdf.numPages; i++) {\n    const page = await pdf.getPage(i);\n    const viewport = page.getViewport({ scale: 2 });\n\n    const canvas = document.createElement('canvas');\n    const context = canvas.getContext('2d')!;\n    canvas.width = viewport.width;\n    canvas.height = viewport.height;\n\n    await page.render({ canvas, canvasContext: context, viewport }).promise;\n\n    const blob = await new Promise<Blob>((resolve) =>\n      canvas.toBlob((b) => b && resolve(b), 'image/png')\n    );\n\n    const filename = `page-${i}.png`;\n    const url = URL.createObjectURL(blob);\n    images.push({ blob, url, filename });\n    zip.file(filename, blob);\n  }\n\n  const zipBuffer = await zip.generateAsync({ type: 'arraybuffer' });\n  const zipFile = new File(\n    [zipBuffer],\n    pdfFile.name.replace(/\\.pdf$/i, '-pages.zip'),\n    { type: 'application/zip' }\n  );\n\n  return { images, zipFile };\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/protect-pdf/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useContext, useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolPdfInput from '@components/input/ToolPdfInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport { InitialValuesType } from './types';\nimport { protectPdf } from './service';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { CustomSnackBarContext } from '../../../../contexts/CustomSnackBarContext';\n\nconst initialValues: InitialValuesType = {\n  password: '',\n  confirmPassword: ''\n};\n\nexport default function ProtectPdf({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [isProcessing, setIsProcessing] = useState<boolean>(false);\n  const { showSnackBar } = useContext(CustomSnackBarContext);\n\n  const compute = async (values: InitialValuesType, input: File | null) => {\n    if (!input) return;\n\n    try {\n      // Validate passwords match\n      if (values.password !== values.confirmPassword) {\n        showSnackBar('Passwords do not match', 'error');\n        return;\n      }\n\n      // Validate password is not empty\n      if (!values.password) {\n        showSnackBar('Password cannot be empty', 'error');\n        return;\n      }\n\n      setIsProcessing(true);\n      const protectedPdf = await protectPdf(input, values);\n      setResult(protectedPdf);\n    } catch (error) {\n      console.error('Error protecting PDF:', error);\n      showSnackBar(\n        `Failed to protect PDF: ${\n          error instanceof Error ? error.message : String(error)\n        }`,\n        'error'\n      );\n      setResult(null);\n    } finally {\n      setIsProcessing(false);\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={initialValues}\n      compute={compute}\n      inputComponent={\n        <ToolPdfInput\n          value={input}\n          onChange={setInput}\n          accept={['application/pdf']}\n          title={'Input PDF'}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          title={'Protected PDF'}\n          value={result}\n          extension={'pdf'}\n          loading={isProcessing}\n          loadingText={'Protecting PDF'}\n        />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: 'Password Settings',\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                title=\"Password\"\n                description=\"Enter a password to protect your PDF\"\n                placeholder=\"Enter password\"\n                type=\"password\"\n                value={values.password}\n                onOwnChange={(value) => updateField('password', value)}\n              />\n              <TextFieldWithDesc\n                title=\"Confirm Password\"\n                description=\"Re-enter your password to confirm\"\n                placeholder=\"Confirm password\"\n                type=\"password\"\n                value={values.confirmPassword}\n                onOwnChange={(value) => updateField('confirmPassword', value)}\n              />\n            </Box>\n          )\n        }\n      ]}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/protect-pdf/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('pdf', {\n  path: 'protect-pdf',\n  icon: 'material-symbols:lock',\n\n  keywords: [\n    'password',\n    'secure',\n    'encrypt',\n    'lock',\n    'private',\n    'confidential',\n    'security',\n    'browser',\n    'encryption'\n  ],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'pdf:protectPdf.title',\n    description: 'pdf:protectPdf.description',\n    shortDescription: 'pdf:protectPdf.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/protect-pdf/service.ts",
    "content": "import { PDFDocument } from 'pdf-lib';\nimport { InitialValuesType } from './types';\nimport {\n  compressWithGhostScript,\n  protectWithGhostScript\n} from '../../../../lib/ghostscript/worker-init';\nimport { loadPDFData } from '../utils';\n\n/**\n * Protects a PDF file with a password\n *\n * @param pdfFile - The PDF file to protect\n * @param options - Protection options including password and protection type\n * @returns A Promise that resolves to a password-protected PDF File\n */\nexport async function protectPdf(\n  pdfFile: File,\n  options: InitialValuesType\n): Promise<File> {\n  // Check if file is a PDF\n  if (pdfFile.type !== 'application/pdf') {\n    throw new Error('The provided file is not a PDF');\n  }\n\n  // Check if passwords match\n  if (options.password !== options.confirmPassword) {\n    throw new Error('Passwords do not match');\n  }\n\n  // Check if password is empty\n  if (!options.password) {\n    throw new Error('Password cannot be empty');\n  }\n\n  const dataObject = {\n    psDataURL: URL.createObjectURL(pdfFile),\n    password: options.password\n  };\n  const protectedFileUrl: string = await protectWithGhostScript(dataObject);\n  return await loadPDFData(\n    protectedFileUrl,\n    pdfFile.name.replace('.pdf', '-protected.pdf')\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/protect-pdf/types.ts",
    "content": "export type ProtectionType = 'owner' | 'user';\n\nexport type InitialValuesType = {\n  password: string;\n  confirmPassword: string;\n};\n"
  },
  {
    "path": "src/pages/tools/pdf/rotate-pdf/index.tsx",
    "content": "import { Box, Typography, FormControlLabel, Switch } from '@mui/material';\nimport { useEffect, useState } from 'react';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { parsePageRanges, rotatePdf } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { PDFDocument } from 'pdf-lib';\nimport ToolPdfInput from '@components/input/ToolPdfInput';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport { InitialValuesType, RotationAngle } from './types';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  rotationAngle: 90,\n  applyToAllPages: true,\n  pageRanges: ''\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Rotate All Pages 90°',\n    description: 'Rotate all pages in the PDF by 90 degrees clockwise',\n    sampleText: '',\n    sampleResult: '',\n    sampleOptions: {\n      rotationAngle: 90,\n      applyToAllPages: true,\n      pageRanges: ''\n    }\n  },\n  {\n    title: 'Rotate Specific Pages 180°',\n    description: 'Rotate pages 1 and 3 by 180 degrees',\n    sampleText: '',\n    sampleResult: '',\n    sampleOptions: {\n      rotationAngle: 180,\n      applyToAllPages: false,\n      pageRanges: '1,3'\n    }\n  },\n  {\n    title: 'Rotate Page Range 270°',\n    description: 'Rotate pages 2 through 5 by 270 degrees',\n    sampleText: '',\n    sampleResult: '',\n    sampleOptions: {\n      rotationAngle: 270,\n      applyToAllPages: false,\n      pageRanges: '2-5'\n    }\n  }\n];\n\nexport default function RotatePdf({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('pdf');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [isProcessing, setIsProcessing] = useState<boolean>(false);\n  const [totalPages, setTotalPages] = useState<number>(0);\n  const [pageRangePreview, setPageRangePreview] = useState<string>('');\n\n  // Get the total number of pages when a PDF is uploaded\n  useEffect(() => {\n    const getPdfInfo = async () => {\n      if (!input) {\n        setTotalPages(0);\n        return;\n      }\n\n      try {\n        const arrayBuffer = await input.arrayBuffer();\n        const pdf = await PDFDocument.load(arrayBuffer);\n        setTotalPages(pdf.getPageCount());\n      } catch (error) {\n        console.error('Error getting PDF info:', error);\n        setTotalPages(0);\n      }\n    };\n\n    getPdfInfo();\n  }, [input]);\n\n  const onValuesChange = (values: InitialValuesType) => {\n    const { pageRanges, applyToAllPages } = values;\n\n    if (applyToAllPages) {\n      setPageRangePreview(\n        totalPages > 0\n          ? t('rotatePdf.allPagesWillBeRotated', { count: totalPages })\n          : ''\n      );\n      return;\n    }\n\n    if (!totalPages || !pageRanges?.trim()) {\n      setPageRangePreview('');\n      return;\n    }\n\n    try {\n      const count = parsePageRanges(pageRanges, totalPages).length;\n      setPageRangePreview(t('rotatePdf.pagesWillBeRotated', { count }));\n    } catch (error) {\n      setPageRangePreview('');\n    }\n  };\n\n  const compute = async (values: InitialValuesType, input: File | null) => {\n    if (!input) return;\n\n    try {\n      setIsProcessing(true);\n      const rotatedPdf = await rotatePdf(input, values);\n      setResult(rotatedPdf);\n    } catch (error) {\n      throw new Error('Error rotating PDF: ' + error);\n    } finally {\n      setIsProcessing(false);\n    }\n  };\n  const angleOptions: { value: RotationAngle; label: string }[] = [\n    { value: 90, label: t('rotatePdf.angleOptions.clockwise90') },\n    { value: 180, label: t('rotatePdf.angleOptions.upsideDown180') },\n    { value: 270, label: t('rotatePdf.angleOptions.counterClockwise270') }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={initialValues}\n      compute={compute}\n      exampleCards={exampleCards}\n      inputComponent={\n        <ToolPdfInput\n          value={input}\n          onChange={setInput}\n          accept={['application/pdf']}\n          title={t('rotatePdf.inputTitle')}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          title={t('rotatePdf.resultTitle')}\n          value={result}\n          extension={'pdf'}\n          loading={isProcessing}\n          loadingText={t('rotatePdf.rotatingPages')}\n        />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('rotatePdf.rotationSettings'),\n          component: (\n            <Box>\n              <Typography variant=\"subtitle2\" sx={{ mb: 1 }}>\n                {t('rotatePdf.rotationAngle')}\n              </Typography>\n              {angleOptions.map((angleOption) => (\n                <SimpleRadio\n                  key={angleOption.value}\n                  title={angleOption.label}\n                  checked={values.rotationAngle === angleOption.value}\n                  onClick={() => {\n                    updateField('rotationAngle', angleOption.value);\n                  }}\n                />\n              ))}\n\n              <Box sx={{ mt: 2 }}>\n                <FormControlLabel\n                  control={\n                    <Switch\n                      checked={values.applyToAllPages}\n                      onChange={(e) => {\n                        updateField('applyToAllPages', e.target.checked);\n                      }}\n                    />\n                  }\n                  label={t('rotatePdf.applyToAllPages')}\n                />\n              </Box>\n\n              {!values.applyToAllPages && (\n                <Box sx={{ mt: 2 }}>\n                  {totalPages > 0 && (\n                    <Typography variant=\"body2\" sx={{ mb: 1 }}>\n                      {t('rotatePdf.pdfPageCount', { count: totalPages })}\n                    </Typography>\n                  )}\n                  <TextFieldWithDesc\n                    value={values.pageRanges}\n                    onOwnChange={(val) => {\n                      updateField('pageRanges', val);\n                    }}\n                    description={t('rotatePdf.pageRangesDescription')}\n                    placeholder={t('rotatePdf.pageRangesPlaceholder')}\n                  />\n                  {pageRangePreview && (\n                    <Typography\n                      variant=\"body2\"\n                      sx={{ mt: 1, color: 'primary.main' }}\n                    >\n                      {pageRangePreview}\n                    </Typography>\n                  )}\n                </Box>\n              )}\n            </Box>\n          )\n        }\n      ]}\n      onValuesChange={onValuesChange}\n      toolInfo={{\n        title: t('rotatePdf.toolInfo.title'),\n        description: t('rotatePdf.toolInfo.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/rotate-pdf/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('pdf', {\n  i18n: {\n    name: 'pdf:rotatePdf.title',\n    description: 'pdf:rotatePdf.description',\n    shortDescription: 'pdf:rotatePdf.shortDescription',\n    longDescription: 'pdf:rotatePdf.longDescription',\n    userTypes: ['generalUsers']\n  },\n\n  path: 'rotate-pdf',\n  icon: 'carbon:rotate',\n\n  keywords: [\n    'rotation',\n    'document',\n    'pages',\n    'orientation',\n    'oritent',\n    'spin',\n    'flip'\n  ],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/rotate-pdf/rotate-pdf.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { parsePageRanges } from './service';\n\ndescribe('rotate-pdf', () => {\n  describe('parsePageRanges', () => {\n    it('should return all pages when pageRanges is empty', () => {\n      const result = parsePageRanges('', 5);\n      expect(result).toEqual([1, 2, 3, 4, 5]);\n    });\n\n    it('should parse single page numbers', () => {\n      const result = parsePageRanges('1,3,5', 5);\n      expect(result).toEqual([1, 3, 5]);\n    });\n\n    it('should parse page ranges', () => {\n      const result = parsePageRanges('2-4', 5);\n      expect(result).toEqual([2, 3, 4]);\n    });\n\n    it('should parse mixed page numbers and ranges', () => {\n      const result = parsePageRanges('1,3-5', 5);\n      expect(result).toEqual([1, 3, 4, 5]);\n    });\n\n    it('should ignore invalid page numbers', () => {\n      const result = parsePageRanges('1,8,3', 5);\n      expect(result).toEqual([1, 3]);\n    });\n\n    it('should handle whitespace', () => {\n      const result = parsePageRanges(' 1, 3 - 5 ', 5);\n      expect(result).toEqual([1, 3, 4, 5]);\n    });\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/rotate-pdf/service.ts",
    "content": "import { degrees, PDFDocument } from 'pdf-lib';\nimport { InitialValuesType } from './types';\n\n/**\n * Parses a page range string and returns an array of page numbers\n * @param pageRangeStr String like \"1,3-5,7\" to extract pages 1, 3, 4, 5, and 7\n * @param totalPages Total number of pages in the PDF\n * @returns Array of page numbers to extract\n */\nexport function parsePageRanges(\n  pageRangeStr: string,\n  totalPages: number\n): number[] {\n  if (!pageRangeStr.trim()) {\n    return Array.from({ length: totalPages }, (_, i) => i + 1);\n  }\n\n  const pageNumbers = new Set<number>();\n  const ranges = pageRangeStr.split(',');\n\n  for (const range of ranges) {\n    const trimmedRange = range.trim();\n\n    if (trimmedRange.includes('-')) {\n      const [start, end] = trimmedRange.split('-').map(Number);\n      if (!isNaN(start) && !isNaN(end)) {\n        // Handle both forward and reversed ranges\n        const normalizedStart = Math.min(start, end);\n        const normalizedEnd = Math.max(start, end);\n\n        for (\n          let i = Math.max(1, normalizedStart);\n          i <= Math.min(totalPages, normalizedEnd);\n          i++\n        ) {\n          pageNumbers.add(i);\n        }\n      }\n    } else {\n      const pageNum = parseInt(trimmedRange, 10);\n      if (!isNaN(pageNum) && pageNum >= 1 && pageNum <= totalPages) {\n        pageNumbers.add(pageNum);\n      }\n    }\n  }\n\n  return [...pageNumbers].sort((a, b) => a - b);\n}\n\n/**\n * Rotates pages in a PDF file\n * @param pdfFile The input PDF file\n * @param options Options including rotation angle and page selection\n * @returns Promise resolving to a new PDF file with rotated pages\n */\nexport async function rotatePdf(\n  pdfFile: File,\n  options: InitialValuesType\n): Promise<File> {\n  const { rotationAngle, applyToAllPages, pageRanges } = options;\n\n  const arrayBuffer = await pdfFile.arrayBuffer();\n  const pdfDoc = await PDFDocument.load(arrayBuffer);\n  const totalPages = pdfDoc.getPageCount();\n\n  // Determine which pages to rotate\n  const pagesToRotate = applyToAllPages\n    ? Array.from({ length: totalPages }, (_, i) => i + 1)\n    : parsePageRanges(pageRanges, totalPages);\n\n  // Apply rotation to selected pages\n  for (const pageNum of pagesToRotate) {\n    const page = pdfDoc.getPage(pageNum - 1);\n    page.setRotation(degrees(rotationAngle));\n  }\n\n  // Save the modified PDF\n  const modifiedPdfBytes = await pdfDoc.save();\n  const newFileName = pdfFile.name.replace('.pdf', '-rotated.pdf');\n\n  return new File([modifiedPdfBytes as any], newFileName, {\n    type: 'application/pdf'\n  });\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/rotate-pdf/types.ts",
    "content": "export type RotationAngle = 90 | 180 | 270;\n\nexport type InitialValuesType = {\n  rotationAngle: RotationAngle;\n  applyToAllPages: boolean;\n  pageRanges: string;\n};\n"
  },
  {
    "path": "src/pages/tools/pdf/split-pdf/index.tsx",
    "content": "import { Box, Typography } from '@mui/material';\nimport { useEffect, useState } from 'react';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { parsePageRanges, splitPdf } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { PDFDocument } from 'pdf-lib';\nimport ToolPdfInput from '@components/input/ToolPdfInput';\nimport { useTranslation } from 'react-i18next';\n\ntype InitialValuesType = {\n  pageRanges: string;\n};\n\nconst initialValues: InitialValuesType = {\n  pageRanges: ''\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Extract Specific Pages',\n    description: 'Extract pages 1, 5, 6, 7, and 8 from a PDF document.',\n    sampleText: '',\n    sampleResult: '',\n    sampleOptions: {\n      pageRanges: '1,5-8'\n    }\n  },\n  {\n    title: 'Extract First and Last Pages',\n    description: 'Extract only the first and last pages from a PDF document.',\n    sampleText: '',\n    sampleResult: '',\n    sampleOptions: {\n      pageRanges: '1,10'\n    }\n  },\n  {\n    title: 'Extract a Range of Pages',\n    description: 'Extract a continuous range of pages from a PDF document.',\n    sampleText: '',\n    sampleResult: '',\n    sampleOptions: {\n      pageRanges: '3-7'\n    }\n  }\n];\n\nexport default function SplitPdf({ title }: ToolComponentProps) {\n  const { t } = useTranslation('pdf');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [isProcessing, setIsProcessing] = useState<boolean>(false);\n  const [totalPages, setTotalPages] = useState<number>(0);\n  const [pageRangePreview, setPageRangePreview] = useState<string>('');\n\n  // Get the total number of pages when a PDF is uploaded\n  useEffect(() => {\n    const getPdfInfo = async () => {\n      if (!input) {\n        setTotalPages(0);\n        return;\n      }\n\n      try {\n        const arrayBuffer = await input.arrayBuffer();\n        const pdf = await PDFDocument.load(arrayBuffer);\n        setTotalPages(pdf.getPageCount());\n      } catch (error) {\n        console.error('Error getting PDF info:', error);\n        setTotalPages(0);\n      }\n    };\n\n    getPdfInfo();\n  }, [input]);\n\n  const onValuesChange = (values: InitialValuesType) => {\n    const { pageRanges } = values;\n    if (!totalPages || !pageRanges?.trim()) {\n      setPageRangePreview('');\n      return;\n    }\n    try {\n      const count = parsePageRanges(pageRanges, totalPages).length;\n      setPageRangePreview(t('splitPdf.pageExtractionPreview', { count }));\n    } catch (error) {\n      setPageRangePreview('');\n    }\n  };\n\n  const compute = async (values: InitialValuesType, input: File | null) => {\n    if (!input) return;\n\n    try {\n      setIsProcessing(true);\n      const splitResult = await splitPdf(input, values.pageRanges);\n      setResult(splitResult);\n    } catch (error) {\n      throw new Error('Error splitting PDF:' + error);\n    } finally {\n      setIsProcessing(false);\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      setInput={setInput}\n      initialValues={initialValues}\n      compute={compute}\n      exampleCards={exampleCards}\n      inputComponent={\n        <ToolPdfInput\n          value={input}\n          onChange={setInput}\n          accept={['application/pdf']}\n          title={t('splitPdf.inputTitle')}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          title={t('splitPdf.resultTitle')}\n          value={result}\n          extension={'pdf'}\n          loading={isProcessing}\n          loadingText={t('splitPdf.extractingPages')}\n        />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('splitPdf.pageSelection'),\n          component: (\n            <Box>\n              {totalPages > 0 && (\n                <Typography variant=\"body2\" sx={{ mb: 1 }}>\n                  {t('splitPdf.pdfPageCount', { count: totalPages })}\n                </Typography>\n              )}\n              <TextFieldWithDesc\n                value={values.pageRanges}\n                onOwnChange={(val) => {\n                  updateField('pageRanges', val);\n                }}\n                description={t('splitPdf.pageRangesDescription')}\n                placeholder={t('splitPdf.pageRangesPlaceholder')}\n              />\n              {pageRangePreview && (\n                <Typography\n                  variant=\"body2\"\n                  sx={{ mt: 1, color: 'primary.main' }}\n                >\n                  {pageRangePreview}\n                </Typography>\n              )}\n            </Box>\n          )\n        }\n      ]}\n      onValuesChange={onValuesChange}\n      toolInfo={{\n        title: t('splitPdf.toolInfo.title'),\n        description: t('splitPdf.toolInfo.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/split-pdf/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const meta = defineTool('pdf', {\n  icon: 'material-symbols-light:call-split-rounded',\n  component: lazy(() => import('./index')),\n  keywords: ['extract', 'pages', 'range', 'document', 'remove'],\n  path: 'split-pdf',\n  i18n: {\n    name: 'pdf:splitPdf.title',\n    description: 'pdf:splitPdf.description',\n    shortDescription: 'pdf:splitPdf.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/split-pdf/service.test.ts",
    "content": "import { parsePageRanges } from './service';\n\ndescribe('parsePageRanges', () => {\n  test('should return all pages when input is empty', () => {\n    expect(parsePageRanges('', 5)).toEqual([1, 2, 3, 4, 5]);\n  });\n\n  test('should parse single page numbers', () => {\n    expect(parsePageRanges('1,3,5', 5)).toEqual([1, 3, 5]);\n  });\n\n  test('should parse page ranges', () => {\n    expect(parsePageRanges('2-4', 5)).toEqual([2, 3, 4]);\n  });\n\n  test('should parse mixed page numbers and ranges', () => {\n    expect(parsePageRanges('1,3-5', 5)).toEqual([1, 3, 4, 5]);\n  });\n\n  test('should handle whitespace', () => {\n    expect(parsePageRanges(' 1, 3 - 5 ', 5)).toEqual([1, 3, 4, 5]);\n  });\n\n  test('should ignore invalid page numbers', () => {\n    expect(parsePageRanges('1,a,3', 5)).toEqual([1, 3]);\n  });\n\n  test('should ignore out-of-range page numbers', () => {\n    expect(parsePageRanges('1,6,3', 5)).toEqual([1, 3]);\n  });\n\n  test('should limit ranges to valid pages', () => {\n    expect(parsePageRanges('0-6', 5)).toEqual([1, 2, 3, 4, 5]);\n  });\n\n  test('should handle reversed ranges', () => {\n    expect(parsePageRanges('4-2', 5)).toEqual([2, 3, 4]);\n  });\n\n  test('should remove duplicates', () => {\n    expect(parsePageRanges('1,1,2,2-4,3', 5)).toEqual([1, 2, 3, 4]);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/pdf/split-pdf/service.ts",
    "content": "import { PDFDocument } from 'pdf-lib';\n\n/**\n * Parses a page range string and returns an array of page numbers\n * @param pageRangeStr String like \"1,3-5,7\" to extract pages 1, 3, 4, 5, and 7\n * @param totalPages Total number of pages in the PDF\n * @returns Array of page numbers to extract\n */\nexport function parsePageRanges(\n  pageRangeStr: string,\n  totalPages: number\n): number[] {\n  if (!pageRangeStr.trim()) {\n    return Array.from({ length: totalPages }, (_, i) => i + 1);\n  }\n\n  const pageNumbers = new Set<number>();\n  const ranges = pageRangeStr.split(',');\n\n  for (const range of ranges) {\n    const trimmedRange = range.trim();\n\n    if (trimmedRange.includes('-')) {\n      const [start, end] = trimmedRange.split('-').map(Number);\n      if (!isNaN(start) && !isNaN(end)) {\n        // Handle both forward and reversed ranges\n        const normalizedStart = Math.min(start, end);\n        const normalizedEnd = Math.max(start, end);\n\n        for (\n          let i = Math.max(1, normalizedStart);\n          i <= Math.min(totalPages, normalizedEnd);\n          i++\n        ) {\n          pageNumbers.add(i);\n        }\n      }\n    } else {\n      const pageNum = parseInt(trimmedRange, 10);\n      if (!isNaN(pageNum) && pageNum >= 1 && pageNum <= totalPages) {\n        pageNumbers.add(pageNum);\n      }\n    }\n  }\n\n  return [...pageNumbers].sort((a, b) => a - b);\n}\n\n/**\n * Splits a PDF file based on specified page ranges\n * @param pdfFile The input PDF file\n * @param pageRanges String specifying which pages to extract (e.g., \"1,3-5,7\")\n * @returns Promise resolving to a new PDF file with only the selected pages\n */\nexport async function splitPdf(\n  pdfFile: File,\n  pageRanges: string\n): Promise<File> {\n  const arrayBuffer = await pdfFile.arrayBuffer();\n  const sourcePdf = await PDFDocument.load(arrayBuffer);\n  const totalPages = sourcePdf.getPageCount();\n  const pagesToExtract = parsePageRanges(pageRanges, totalPages);\n\n  const newPdf = await PDFDocument.create();\n  const copiedPages = await newPdf.copyPages(\n    sourcePdf,\n    pagesToExtract.map((pageNum) => pageNum - 1)\n  );\n  copiedPages.forEach((page) => newPdf.addPage(page));\n\n  const newPdfBytes = await newPdf.save();\n  const newFileName = pdfFile.name.replace('.pdf', '-extracted.pdf');\n  return new File([newPdfBytes as any], newFileName, {\n    type: 'application/pdf'\n  });\n}\n"
  },
  {
    "path": "src/pages/tools/pdf/utils.ts",
    "content": "export function loadPDFData(url: string, filename: string): Promise<File> {\n  return new Promise((resolve) => {\n    const xhr = new XMLHttpRequest();\n    xhr.open('GET', url);\n    xhr.responseType = 'arraybuffer';\n    xhr.onload = function () {\n      window.URL.revokeObjectURL(url);\n      const blob = new Blob([xhr.response], { type: 'application/pdf' });\n      const newFile = new File([blob], filename, {\n        type: 'application/pdf'\n      });\n      resolve(newFile);\n    };\n    xhr.send();\n  });\n}\n"
  },
  {
    "path": "src/pages/tools/string/base64/base64.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { base64 } from './service';\n\ndescribe('base64', () => {\n  it('should encode a simple string using Base64 correctly', () => {\n    const input = 'hello';\n    const result = base64(input, true);\n    expect(result).toBe('aGVsbG8=');\n  });\n\n  it('should decode a simple Base64-encoded string correctly', () => {\n    const input = 'aGVsbG8=';\n    const result = base64(input, false);\n    expect(result).toBe('hello');\n  });\n\n  it('should handle special characters encoding correctly', () => {\n    const input = 'Hello, World!';\n    const result = base64(input, true);\n    expect(result).toBe('SGVsbG8sIFdvcmxkIQ==');\n  });\n\n  it('should handle special characters decoding correctly', () => {\n    const input = 'SGVsbG8sIFdvcmxkIQ==';\n    const result = base64(input, false);\n    expect(result).toBe('Hello, World!');\n  });\n\n  it('should handle an empty string encoding correctly', () => {\n    const input = '';\n    const result = base64(input, true);\n    expect(result).toBe('');\n  });\n\n  it('should handle an empty string decoding correctly', () => {\n    const input = '';\n    const result = base64(input, false);\n    expect(result).toBe('');\n  });\n\n  it('should handle a newline encoding correctly', () => {\n    const input = '\\n';\n    const result = base64(input, true);\n    expect(result).toBe('Cg==');\n  });\n\n  it('should handle a newline decoding correctly', () => {\n    const input = 'Cg==';\n    const result = base64(input, false);\n    expect(result).toBe('\\n');\n  });\n\n  it('should handle a string with symbols encoding correctly', () => {\n    const input = '!@#$%^&*()_+-=';\n    const result = base64(input, true);\n    expect(result).toBe('IUAjJCVeJiooKV8rLT0=');\n  });\n\n  it('should handle a string with mixed characters decoding correctly', () => {\n    const input = 'IUAjJCVeJiooKV8rLT0=';\n    const result = base64(input, false);\n    expect(result).toBe('!@#$%^&*()_+-=');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/base64/index.tsx",
    "content": "import { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { base64 } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { Box } from '@mui/material';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport { InitialValuesType } from './types';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  mode: 'encode'\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Encode data in UTF-8 with Base64',\n    description: 'This example shows how to encode a simple text using Base64.',\n    sampleText: 'Hello, World!',\n    sampleResult: 'SGVsbG8sIFdvcmxkIQ==',\n    sampleOptions: { mode: 'encode' }\n  },\n  {\n    title: 'Decode Base64-encoded data to UTF-8',\n    description:\n      'This example shows how to decode data that was encoded with Base64.',\n    sampleText: 'SGVsbG8sIFdvcmxkIQ==',\n    sampleResult: 'Hello, World!',\n    sampleOptions: { mode: 'decode' }\n  }\n];\n\nexport default function Base64({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: string) => {\n    if (input) setResult(base64(input, optionsValues.mode === 'encode'));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('base64.optionsTitle'),\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('mode', 'encode')}\n            checked={values.mode === 'encode'}\n            title={t('base64.encode')}\n          />\n          <SimpleRadio\n            onClick={() => updateField('mode', 'decode')}\n            checked={values.mode === 'decode'}\n            title={t('base64.decode')}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolTextInput\n          title={t('base64.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('base64.resultTitle')} value={result} />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      toolInfo={{\n        title: t('base64.toolInfo.title'),\n        description: t('base64.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n      input={input}\n      setInput={setInput}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/base64/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'base64',\n  icon: 'tabler:number-64-small',\n\n  keywords: ['b64'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:base64.title',\n    description: 'string:base64.description',\n    shortDescription: 'string:base64.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/base64/service.ts",
    "content": "import { Buffer } from 'buffer';\n\nexport function base64(input: string, encode: boolean): string {\n  return encode\n    ? Buffer.from(input, 'utf-8').toString('base64')\n    : Buffer.from(input, 'base64').toString('utf-8');\n}\n"
  },
  {
    "path": "src/pages/tools/string/base64/types.ts",
    "content": "export type InitialValuesType = {\n  mode: 'encode' | 'decode';\n};\n"
  },
  {
    "path": "src/pages/tools/string/censor/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport { useState } from 'react';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { censorText } from './service';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport { InitialValuesType } from './types';\nimport ToolContent from '@components/ToolContent';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\n\nconst initialValues: InitialValuesType = {\n  wordsToCensor: '',\n  censoredBySymbol: true,\n  censorSymbol: '█',\n  eachLetter: true,\n  censorWord: 'CENSORED'\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Censor a Word in a Quote',\n    description: `In this example, we hide the unpleasant word \"idiot\" from Jim Rohn's quote. We specify this word in the words-to-censor option and mask it with a neat smiling face character \"☺\".`,\n    sampleText:\n      'Motivation alone is not enough. If you have an idiot and you motivate him, now you have a motivated idiot. Jim Rohn',\n    sampleResult:\n      'Motivation alone is not enough. If you have an ☺ and you motivate him, now you have a motivated ☺. Jim Rohn',\n    sampleOptions: {\n      ...initialValues,\n      wordsToCensor: 'idiot',\n      censorSymbol: '☺',\n      eachLetter: false\n    }\n  },\n  {\n    title: 'Censor an Excerpt',\n    description: `In this example, we censor multiple words from an excerpt from the novel \"The Guns of Avalon\" by Roger Zelazny. To do this, we write out all unnecessary words in the multi-line text option and select the \"Use a Symbol to Censor\" censoring mode. We activate the \"Mask Each Letter\" option so that in place of each word exactly as many block characters \"█\" appeared as there are letters in that word.`,\n    sampleText:\n      '“In the mirrors of the many judgments, my hands are the color of blood. I sometimes fancy myself an evil which exists to oppose other evils; and on that great Day of which the prophets speak but in which they do not truly believe, on the day the world is utterly cleansed of evil, then I too will go down into darkness, swallowing curses. Until then, I will not wash my hands nor let them hang useless.” ― Roger Zelazny, The Guns of Avalon',\n    sampleResult:\n      '“In the mirrors of the many judgments, my hands are the color of █████. I sometimes fancy myself an ████ which exists to oppose other █████; and on that great Day of which the prophets speak but in which they do not truly believe, on the day the world is utterly cleansed of ████, then I too will go down into ████████, swallowing ██████. Until then, I will not wash my hands nor let them hang useless.” ― Roger Zelazny, The Guns of Avalon',\n    sampleOptions: {\n      ...initialValues,\n      wordsToCensor: 'blood\\nevil\\ndarkness\\ncurses',\n      eachLetter: true\n    }\n  },\n  {\n    title: \"Censor Agent's Name\",\n    description: `In this example, we hide the name of an undercover FBI agent. We replace two words at once (first name and last name) with the code name \"Agent 007\"`,\n    sampleText:\n      'My name is John and I am an undercover FBI agent. I usually write my name in lowercase as \"john\" because I find uppercase letters scary. Unfortunately, in documents, my name is properly capitalized as John and it makes me upset.',\n    sampleResult:\n      'My name is Agent 007 and I am an undercover FBI agent. I usually write my name in lowercase as \"Agent 007\" because I find uppercase letters scary. Unfortunately, in documents, my name is properly capitalized as Agent 007 and it makes me upset.',\n    sampleOptions: {\n      ...initialValues,\n      censoredBySymbol: false,\n      wordsToCensor: 'john',\n      censorWord: 'Agent 007'\n    }\n  }\n];\n\nexport default function CensorText({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  function compute(initialValues: InitialValuesType, input: string) {\n    setResult(censorText(input, initialValues));\n  }\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Words to Censor',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            multiline\n            rows={3}\n            value={values.wordsToCensor}\n            onOwnChange={(val) => updateField('wordsToCensor', val)}\n            description={`Specify all unwanted words that\n                 you want to hide in text (separated by a new line)`}\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Censor Mode',\n      component: (\n        <Box>\n          <SelectWithDesc\n            selected={values.censoredBySymbol}\n            options={[\n              { label: 'Censor by Symbol', value: true },\n              { label: 'Censor by Word', value: false }\n            ]}\n            onChange={(value) => updateField('censoredBySymbol', value)}\n            description={'Select the censoring mode.'}\n          />\n\n          {values.censoredBySymbol && (\n            <TextFieldWithDesc\n              value={values.censorSymbol}\n              onOwnChange={(val) => updateField('censorSymbol', val)}\n              description={`A symbol, character, or pattern to use for censoring.`}\n            />\n          )}\n\n          {values.censoredBySymbol && (\n            <CheckboxWithDesc\n              checked={values.eachLetter}\n              onChange={(value) => updateField('eachLetter', value)}\n              title=\"Mask each letter\"\n              description=\"Put a masking symbol in place of each letter of the censored word.\"\n            />\n          )}\n\n          {!values.censoredBySymbol && (\n            <TextFieldWithDesc\n              value={values.censorWord}\n              onOwnChange={(val) => updateField('censorWord', val)}\n              description={`Replace all censored words with this word.`}\n            />\n          )}\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput title={'Input text'} value={input} onChange={setInput} />\n      }\n      resultComponent={\n        <ToolTextResult title={'Censored text'} value={result} />\n      }\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/censor/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'censor',\n\n  icon: 'hugeicons:text-footnote',\n\n  keywords: ['text', 'censor', 'words', 'characters'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:censor.title',\n    description: 'string:censor.description',\n    shortDescription: 'string:censor.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/censor/service.ts",
    "content": "import { InitialValuesType } from './types';\n\nexport function censorText(input: string, options: InitialValuesType): string {\n  if (!input) return '';\n  if (!options.wordsToCensor) return input;\n\n  if (options.censoredBySymbol && !isSymbol(options.censorSymbol)) {\n    throw new Error('Enter a valid censor symbol (non-alphanumeric or emoji)');\n  }\n\n  const wordsToCensor = options.wordsToCensor\n    .split('\\n')\n    .map((word) => word.trim())\n    .filter((word) => word.length > 0);\n\n  let censoredText = input;\n\n  for (const word of wordsToCensor) {\n    const escapedWord = escapeRegex(word);\n    const pattern = new RegExp(`\\\\b${escapedWord}\\\\b`, 'giu');\n\n    const replacement = options.censoredBySymbol\n      ? options.eachLetter\n        ? options.censorSymbol.repeat(word.length)\n        : options.censorSymbol\n      : options.censorWord;\n\n    censoredText = censoredText.replace(pattern, replacement);\n  }\n\n  return censoredText;\n}\n\n/**\n * Escapes RegExp special characters in a string\n */\nfunction escapeRegex(str: string): string {\n  return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Determines if a string is a valid symbol or emoji (multi-codepoint supported).\n */\nfunction isSymbol(input: string): boolean {\n  return (\n    /^[^\\p{L}\\p{N}]+$/u.test(input) || // Not a letter or number\n    /\\p{Extended_Pictographic}/u.test(input) // Emoji or pictographic symbol\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/censor/types.ts",
    "content": "export type InitialValuesType = {\n  wordsToCensor: string;\n  censoredBySymbol: boolean;\n  censorSymbol: string;\n  eachLetter: boolean;\n  censorWord: string;\n};\n"
  },
  {
    "path": "src/pages/tools/string/create-palindrome/create-palindrome.service.test.ts",
    "content": "import { describe, expect } from 'vitest';\nimport { createPalindrome, createPalindromeList } from './service';\n\ndescribe('createPalindrome', () => {\n  test('should create palindrome by reversing the entire string', () => {\n    const input = 'hello';\n    const result = createPalindrome(input, true);\n    expect(result).toBe('helloolleh');\n  });\n\n  test('should create palindrome by reversing the string excluding the last character', () => {\n    const input = 'hello';\n    const result = createPalindrome(input, false);\n    expect(result).toBe('hellolleh');\n  });\n\n  test('should return an empty string if input is empty', () => {\n    const input = '';\n    const result = createPalindrome(input, true);\n    expect(result).toBe('');\n  });\n});\n\ndescribe('createPalindromeList', () => {\n  test('should create palindrome for single-line input', () => {\n    const input = 'hello';\n    const result = createPalindromeList(input, true, false);\n    expect(result).toBe('helloolleh');\n  });\n\n  test('should create palindrome for single-line input considering trailing spaces', () => {\n    const input = 'hello ';\n    const result = createPalindromeList(input, true, false);\n    expect(result).toBe('hello  olleh');\n  });\n\n  test('should create palindrome for single-line input ignoring trailing spaces if lastChar is set to false', () => {\n    const input = 'hello ';\n    const result = createPalindromeList(input, true, false);\n    expect(result).toBe('hello  olleh');\n  });\n\n  test('should create palindrome for multi-line input', () => {\n    const input = 'hello\\nworld';\n    const result = createPalindromeList(input, true, true);\n    expect(result).toBe('helloolleh\\nworlddlrow');\n  });\n\n  test('should create palindrome for no multi-line input', () => {\n    const input = 'hello\\nworld\\n';\n    const result = createPalindromeList(input, true, false);\n    expect(result).toBe('hello\\nworld\\n\\ndlrow\\nolleh');\n  });\n\n  test('should handle multi-line input with lastChar set to false', () => {\n    const input = 'hello\\nworld';\n    const result = createPalindromeList(input, false, true);\n    expect(result).toBe('hellolleh\\nworldlrow');\n  });\n\n  test('should return an empty string if input is empty', () => {\n    const input = '';\n    const result = createPalindromeList(input, true, false);\n    expect(result).toBe('');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/create-palindrome/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { createPalindromeList } from './service';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolContent from '@components/ToolContent';\n\nconst initialValues = {\n  lastChar: true,\n  multiLine: false\n};\n\nconst exampleCards: CardExampleType<typeof initialValues>[] = [\n  {\n    title: 'Create Simple Palindrome',\n    description:\n      'Creates a palindrome by repeating the text in reverse order, including the last character.',\n    sampleText: 'level',\n    sampleResult: 'levellevel',\n    sampleOptions: {\n      ...initialValues,\n      lastChar: true\n    }\n  },\n  {\n    title: 'Create Palindrome Without Last Character Duplication',\n    description:\n      'Creates a palindrome without repeating the last character in the reverse part.',\n    sampleText: 'radar',\n    sampleResult: 'radarada',\n    sampleOptions: {\n      ...initialValues,\n      lastChar: false\n    }\n  },\n  {\n    title: 'Multi-line Palindrome Creation',\n    description: 'Creates palindromes for each line independently.',\n    sampleText: 'mom\\ndad\\nwow',\n    sampleResult: 'mommom\\ndaddad\\nwowwow',\n    sampleOptions: {\n      ...initialValues,\n      lastChar: true,\n      multiLine: true\n    }\n  }\n];\n\nexport default function CreatePalindrome({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const computeExternal = (\n    optionsValues: typeof initialValues,\n    input: string\n  ) => {\n    const { lastChar, multiLine } = optionsValues;\n    setResult(createPalindromeList(input, lastChar, multiLine));\n  };\n\n  const getGroups: GetGroupsType<typeof initialValues> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Palindrome options',\n      component: [\n        <CheckboxWithDesc\n          key=\"lastChar\"\n          checked={values.lastChar}\n          title=\"Include last character\"\n          description=\"Repeat the last character in the reversed part\"\n          onChange={(val) => updateField('lastChar', val)}\n        />,\n        <CheckboxWithDesc\n          key=\"multiLine\"\n          checked={values.multiLine}\n          title=\"Process multi-line text\"\n          description=\"Create palindromes for each line independently\"\n          onChange={(val) => updateField('multiLine', val)}\n        />\n      ]\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={computeExternal}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput title={'Input text'} value={input} onChange={setInput} />\n      }\n      resultComponent={\n        <ToolTextResult title={'Palindrome text'} value={result} />\n      }\n      toolInfo={{\n        title: 'What Is a String Palindrome Creator?',\n        description: longDescription\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/create-palindrome/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('string', {\n  path: 'create-palindrome',\n  icon: 'material-symbols-light:repeat',\n\n  keywords: ['create', 'palindrome'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:createPalindrome.title',\n    description: 'string:createPalindrome.description',\n    shortDescription: 'string:createPalindrome.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/create-palindrome/service.ts",
    "content": "import { reverseString } from 'utils/string';\n\nexport function createPalindrome(\n  input: string,\n  lastChar: boolean // only checkbox is need here to handle it [instead of two combo boxes]\n) {\n  if (!input) return '';\n  let result: string;\n  let reversedString: string;\n\n  // reverse the whole input if lastChar enabled\n  reversedString = lastChar\n    ? reverseString(input)\n    : reverseString(input.slice(0, -1));\n  result = input.concat(reversedString);\n  return result;\n}\n\nexport function createPalindromeList(\n  input: string,\n  lastChar: boolean,\n  multiLine: boolean\n): string {\n  if (!input) return '';\n  let array: string[];\n  const result: string[] = [];\n\n  if (!multiLine) return createPalindrome(input, lastChar);\n  else {\n    array = input.split('\\n');\n    for (const word of array) {\n      result.push(createPalindrome(word, lastChar));\n    }\n  }\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/string/extract-substring/extract-substring.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { extractSubstring } from './service';\n\ndescribe('extractSubstring', () => {\n  it('should extract a substring from single-line input', () => {\n    const input = 'hello world';\n    const result = extractSubstring(input, 1, 4, false, false);\n    expect(result).toBe('hell');\n  });\n\n  it('should extract and reverse a substring from single-line input', () => {\n    const input = 'hello world';\n    const result = extractSubstring(input, 1, 5, false, true);\n    expect(result).toBe('olleh');\n  });\n\n  it('should extract substrings from multi-line input', () => {\n    const input = 'hello\\nworld';\n    const result = extractSubstring(input, 1, 5, true, false);\n    expect(result).toBe('hello\\nworld');\n  });\n\n  it('should extract and reverse substrings from multi-line input', () => {\n    const input = 'hello\\nworld';\n    const result = extractSubstring(input, 1, 4, true, true);\n    expect(result).toBe('lleh\\nlrow');\n  });\n\n  it('should handle empty input', () => {\n    const input = '';\n    const result = extractSubstring(input, 1, 5, false, false);\n    expect(result).toBe('');\n  });\n\n  it('should handle start and length out of bounds', () => {\n    const input = 'hello';\n    const result = extractSubstring(input, 10, 5, false, false);\n    expect(result).toBe('');\n  });\n\n  it('should handle negative start and length', () => {\n    expect(() => extractSubstring('hello', -1, 5, false, false)).toThrow(\n      'Start index must be greater than zero.'\n    );\n    expect(() => extractSubstring('hello', 1, -5, false, false)).toThrow(\n      'Length value must be greater than or equal to zero.'\n    );\n  });\n\n  it('should handle zero length', () => {\n    const input = 'hello';\n    const result = extractSubstring(input, 1, 0, false, false);\n    expect(result).toBe('');\n  });\n\n  it('should work', () => {\n    const input = 'je me nomme king\\n22 est mon chiffre';\n    const result = extractSubstring(input, 12, 7, true, false);\n    expect(result).toBe(' king\\nchiffre');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/extract-substring/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { extractSubstring } from './service';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolContent from '@components/ToolContent';\n\nconst initialValues = {\n  start: '1',\n  length: '5',\n  multiLine: false,\n  reverse: false\n};\n\nconst exampleCards: CardExampleType<typeof initialValues>[] = [\n  {\n    title: 'Extract First 5 Characters',\n    description: 'This example extracts the first 5 characters from the text.',\n    sampleText: 'The quick brown fox jumps over the lazy dog.',\n    sampleResult: 'The q',\n    sampleOptions: {\n      ...initialValues,\n      start: '1',\n      length: '5'\n    }\n  },\n  {\n    title: 'Extract Words from the Middle',\n    description:\n      'Extract a substring starting from position 11 with a length of 10 characters.',\n    sampleText: 'The quick brown fox jumps over the lazy dog.',\n    sampleResult: 'brown fox',\n    sampleOptions: {\n      ...initialValues,\n      start: '11',\n      length: '10'\n    }\n  },\n  {\n    title: 'Multi-line Extraction with Reversal',\n    description: 'Extract characters 1-3 from each line and reverse them.',\n    sampleText: 'First line\\nSecond line\\nThird line',\n    sampleResult: 'riF\\nceS\\nihT',\n    sampleOptions: {\n      ...initialValues,\n      start: '1',\n      length: '3',\n      multiLine: true,\n      reverse: true\n    }\n  }\n];\n\nexport default function ExtractSubstring({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const computeExternal = (\n    optionsValues: typeof initialValues,\n    input: string\n  ) => {\n    const { start, length, multiLine, reverse } = optionsValues;\n    try {\n      setResult(\n        extractSubstring(\n          input,\n          parseInt(start, 10),\n          parseInt(length, 10),\n          multiLine,\n          reverse\n        )\n      );\n    } catch (error) {\n      if (error instanceof Error) {\n        setResult(`Error: ${error.message}`);\n      } else {\n        setResult('An unknown error occurred');\n      }\n    }\n  };\n\n  const getGroups: GetGroupsType<typeof initialValues> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Extraction options',\n      component: [\n        <TextFieldWithDesc\n          key=\"start\"\n          value={values.start}\n          onOwnChange={(value) => updateField('start', value)}\n          description=\"Start position (1-based index)\"\n          type=\"number\"\n        />,\n        <TextFieldWithDesc\n          key=\"length\"\n          value={values.length}\n          onOwnChange={(value) => updateField('length', value)}\n          description=\"Number of characters to extract\"\n          type=\"number\"\n        />,\n        <CheckboxWithDesc\n          key=\"multiLine\"\n          checked={values.multiLine}\n          title=\"Process multi-line text\"\n          description=\"Extract from each line independently\"\n          onChange={(val) => updateField('multiLine', val)}\n        />,\n        <CheckboxWithDesc\n          key=\"reverse\"\n          checked={values.reverse}\n          title=\"Reverse output\"\n          description=\"Reverse the extracted substring\"\n          onChange={(val) => updateField('reverse', val)}\n        />\n      ]\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={computeExternal}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput title={'Input text'} value={input} onChange={setInput} />\n      }\n      resultComponent={\n        <ToolTextResult title={'Extracted text'} value={result} />\n      }\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/extract-substring/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('string', {\n  path: 'extract-substring',\n  icon: 'material-symbols-light:content-cut',\n\n  keywords: ['extract', 'substring'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:extractSubstring.title',\n    description: 'string:extractSubstring.description',\n    shortDescription: 'string:extractSubstring.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/extract-substring/service.ts",
    "content": "import { reverseString } from 'utils/string';\n\nexport function extractSubstring(\n  input: string,\n  start: number,\n  length: number,\n  multiLine: boolean,\n  reverse: boolean\n): string {\n  if (!input) return '';\n  // edge Cases\n  if (start <= 0) throw new Error('Start index must be greater than zero.');\n  if (length < 0)\n    throw new Error('Length value must be greater than or equal to zero.');\n  if (length === 0) return '';\n\n  let array: string[];\n  let result: string[] = [];\n\n  const extract = (str: string, start: number, length: number): string => {\n    const end = start - 1 + length;\n    if (start - 1 >= str.length) return '';\n    return str.substring(start - 1, Math.min(end, str.length));\n  };\n\n  if (!multiLine) {\n    result.push(extract(input, start, length));\n  } else {\n    array = input.split('\\n');\n    for (const word of array) {\n      result.push(extract(word, start, length));\n    }\n  }\n  result = reverse ? result.map((word) => reverseString(word)) : result;\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/string/hidden-character-detector/hidden-character-detector.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { analyzeHiddenCharacters, main } from './service';\nimport { InitialValuesType } from './types';\n\ndescribe('Hidden Character Detector', () => {\n  const defaultOptions: InitialValuesType = {\n    showUnicodeCodes: true,\n    highlightRTL: true,\n    showInvisibleChars: true,\n    includeZeroWidthChars: true\n  };\n\n  describe('analyzeHiddenCharacters', () => {\n    it('should detect RTL Override characters', () => {\n      const text = 'Hello\\u202EWorld'; // RTL Override\n      const result = analyzeHiddenCharacters(text, defaultOptions);\n\n      expect(result.totalHiddenChars).toBe(1);\n      expect(result.hasRTLOverride).toBe(true);\n      expect(result.hiddenCharacters[0].isRTL).toBe(true);\n      expect(result.hiddenCharacters[0].unicode).toBe('U+202E');\n      expect(result.hiddenCharacters[0].name).toBe('Right-to-Left Override');\n    });\n\n    it('should detect invisible characters', () => {\n      const text = 'Hello\\u200BWorld'; // Zero Width Space\n      const result = analyzeHiddenCharacters(text, defaultOptions);\n\n      expect(result.totalHiddenChars).toBe(1);\n      expect(result.hasInvisibleChars).toBe(true);\n      expect(result.hiddenCharacters[0].isInvisible).toBe(true);\n      expect(result.hiddenCharacters[0].unicode).toBe('U+200B');\n      expect(result.hiddenCharacters[0].name).toBe('Zero Width Space');\n    });\n\n    it('should detect zero-width characters', () => {\n      const text = 'Hello\\u200CWorld'; // Zero Width Non-Joiner\n      const result = analyzeHiddenCharacters(text, defaultOptions);\n\n      expect(result.totalHiddenChars).toBe(1);\n      expect(result.hasZeroWidthChars).toBe(true);\n      expect(result.hiddenCharacters[0].isZeroWidth).toBe(true);\n      expect(result.hiddenCharacters[0].unicode).toBe('U+200C');\n    });\n\n    it('should detect multiple hidden characters', () => {\n      const text = 'Hello\\u202E\\u200BWorld'; // RTL Override + Zero Width Space\n      const result = analyzeHiddenCharacters(text, defaultOptions);\n\n      expect(result.totalHiddenChars).toBe(2);\n      expect(result.hasRTLOverride).toBe(true);\n      expect(result.hasInvisibleChars).toBe(true);\n      expect(result.hasZeroWidthChars).toBe(true);\n    });\n\n    it('should detect control characters', () => {\n      const text = 'Hello\\u0000World'; // Null character\n      const result = analyzeHiddenCharacters(text, defaultOptions);\n\n      expect(result.totalHiddenChars).toBe(1);\n      expect(result.hiddenCharacters[0].category).toBe('Control Character');\n      expect(result.hiddenCharacters[0].isInvisible).toBe(true);\n    });\n\n    it('should not detect regular characters', () => {\n      const text = 'Hello World';\n      const result = analyzeHiddenCharacters(text, defaultOptions);\n\n      expect(result.totalHiddenChars).toBe(0);\n      expect(result.hasRTLOverride).toBe(false);\n      expect(result.hasInvisibleChars).toBe(false);\n      expect(result.hasZeroWidthChars).toBe(false);\n    });\n\n    it('should filter based on options', () => {\n      const text = 'Hello\\u202E\\u200BWorld';\n      const options: InitialValuesType = {\n        ...defaultOptions,\n        highlightRTL: false,\n        showInvisibleChars: true\n      };\n\n      const result = analyzeHiddenCharacters(text, options);\n\n      expect(result.totalHiddenChars).toBe(1); // Only invisible chars\n      expect(result.hasRTLOverride).toBe(false);\n      expect(result.hasInvisibleChars).toBe(true);\n    });\n\n    it('should provide correct character positions', () => {\n      const text = 'Hello\\u202EWorld';\n      const result = analyzeHiddenCharacters(text, defaultOptions);\n\n      expect(result.hiddenCharacters[0].position).toBe(5);\n      expect(result.hiddenCharacters[0].char).toBe('\\u202E');\n    });\n  });\n\n  describe('main function', () => {\n    it('should return message when no hidden characters found', () => {\n      const text = 'Hello World';\n      const result = main(text, defaultOptions);\n\n      expect(result).toBe('No hidden characters detected in the text.');\n    });\n\n    it('should return detailed analysis when hidden characters found', () => {\n      const text = 'Hello\\u202EWorld';\n      const result = main(text, defaultOptions);\n\n      expect(result).toContain('Found 1 hidden character(s):');\n      expect(result).toContain('Position 5: Right-to-Left Override (U+202E)');\n      expect(result).toContain('Category: RTL Override');\n      expect(result).toContain('⚠️  RTL Override Character');\n      expect(result).toContain('WARNING: RTL Override characters detected!');\n    });\n\n    it('should include Unicode codes when showUnicodeCodes is true', () => {\n      const text = 'Hello\\u200BWorld';\n      const options: InitialValuesType = {\n        ...defaultOptions,\n        showUnicodeCodes: true\n      };\n\n      const result = main(text, options);\n\n      expect(result).toContain('Unicode: U+200B');\n    });\n\n    it('should not include Unicode codes when showUnicodeCodes is false', () => {\n      const text = 'Hello\\u200BWorld';\n      const options: InitialValuesType = {\n        ...defaultOptions,\n        showUnicodeCodes: false\n      };\n\n      const result = main(text, options);\n\n      expect(result).not.toContain('Unicode: U+200B');\n    });\n\n    it('should handle multiple RTL characters', () => {\n      const text = 'Hello\\u202E\\u202DWorld';\n      const result = main(text, defaultOptions);\n\n      expect(result).toContain('Found 2 hidden character(s):');\n      expect(result).toContain('Right-to-Left Override');\n      expect(result).toContain('Left-to-Right Override');\n    });\n\n    it('should handle mixed character types', () => {\n      const text = 'Hello\\u202E\\u200B\\u200CWorld';\n      const result = main(text, defaultOptions);\n\n      expect(result).toContain('Found 3 hidden character(s):');\n      expect(result).toContain('RTL Override Character');\n      expect(result).toContain('Invisible Character');\n      expect(result).toContain('Zero Width Character');\n    });\n  });\n\n  describe('edge cases', () => {\n    it('should handle empty string', () => {\n      const result = analyzeHiddenCharacters('', defaultOptions);\n\n      expect(result.totalHiddenChars).toBe(0);\n      expect(result.originalText).toBe('');\n    });\n\n    it('should handle string with only hidden characters', () => {\n      const text = '\\u202E\\u200B\\u200C';\n      const result = analyzeHiddenCharacters(text, defaultOptions);\n\n      expect(result.totalHiddenChars).toBe(3);\n      expect(result.hasRTLOverride).toBe(true);\n      expect(result.hasInvisibleChars).toBe(true);\n      expect(result.hasZeroWidthChars).toBe(true);\n    });\n\n    it('should handle very long strings', () => {\n      const text = 'A'.repeat(1000) + '\\u202E' + 'B'.repeat(1000);\n      const result = analyzeHiddenCharacters(text, defaultOptions);\n\n      expect(result.totalHiddenChars).toBe(1);\n      expect(result.hiddenCharacters[0].position).toBe(1000);\n    });\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/hidden-character-detector/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Alert, Box, Paper, Typography } from '@mui/material';\nimport { useTranslation } from 'react-i18next';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { InitialValuesType } from './types';\nimport { analyzeHiddenCharacters } from './service';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\n\nconst initialValues: InitialValuesType = {\n  showUnicodeCodes: true,\n  highlightRTL: true,\n  showInvisibleChars: true,\n  includeZeroWidthChars: true\n};\n\nexport default function HiddenCharacterDetector({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState('');\n  const [result, setResult] = useState<string>('');\n  const [analysis, setAnalysis] = useState<any>(null);\n\n  const compute = (values: InitialValuesType, input: string) => {\n    if (!input.trim()) return;\n\n    try {\n      const analysisResult = analyzeHiddenCharacters(input, values);\n      setAnalysis(analysisResult);\n\n      if (analysisResult.totalHiddenChars === 0) {\n        setResult(t('hiddenCharacterDetector.noHiddenChars'));\n      } else {\n        let output = t('hiddenCharacterDetector.foundChars', {\n          count: analysisResult.totalHiddenChars\n        });\n\n        analysisResult.hiddenCharacters.forEach((char: any) => {\n          output += `${t('hiddenCharacterDetector.position')} ${\n            char.position\n          }: ${char.name} (${char.unicode})\\n`;\n          if (values.showUnicodeCodes) {\n            output += `  ${t('hiddenCharacterDetector.unicode')}: ${\n              char.unicode\n            }\\n`;\n          }\n          output += `  ${t('hiddenCharacterDetector.category')}: ${\n            char.category\n          }\\n`;\n          if (char.isRTL)\n            output += `  ⚠️  ${t('hiddenCharacterDetector.rtlOverride')}\\n`;\n          if (char.isInvisible)\n            output += `  👁️  ${t('hiddenCharacterDetector.invisibleChar')}\\n`;\n          if (char.isZeroWidth)\n            output += `  📏  ${t('hiddenCharacterDetector.zeroWidthChar')}\\n`;\n          output += '\\n';\n        });\n\n        if (analysisResult.hasRTLOverride) {\n          output += `⚠️  ${t('hiddenCharacterDetector.rtlWarning')}\\n`;\n        }\n\n        setResult(output);\n      }\n    } catch (error) {\n      setResult(`Error: ${error}`);\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <Box>\n          <ToolTextInput\n            value={input}\n            onChange={setInput}\n            title={t('hiddenCharacterDetector.inputTitle')}\n            placeholder={t('hiddenCharacterDetector.inputPlaceholder')}\n          />\n\n          {analysis && analysis.hasRTLOverride && (\n            <Alert severity=\"warning\" sx={{ mt: 2 }}>\n              {t('hiddenCharacterDetector.rtlAlert')}\n            </Alert>\n          )}\n\n          {analysis && analysis.totalHiddenChars > 0 && (\n            <Paper sx={{ p: 2, mt: 2, backgroundColor: '#fff3cd' }}>\n              <Typography variant=\"h6\" gutterBottom>\n                {t('hiddenCharacterDetector.summary')}\n              </Typography>\n              <Typography variant=\"body2\">\n                {t('hiddenCharacterDetector.totalChars', {\n                  count: analysis.totalHiddenChars\n                })}\n                {analysis.hasRTLOverride &&\n                  ` • ${t('hiddenCharacterDetector.rtlFound')}`}\n                {analysis.hasInvisibleChars &&\n                  ` • ${t('hiddenCharacterDetector.invisibleFound')}`}\n                {analysis.hasZeroWidthChars &&\n                  ` • ${t('hiddenCharacterDetector.zeroWidthFound')}`}\n              </Typography>\n            </Paper>\n          )}\n        </Box>\n      }\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      getGroups={null}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      toolInfo={{\n        title: `What is ${title}?`,\n        description:\n          longDescription ||\n          'A tool to detect hidden Unicode characters, especially RTL Override characters that could be used in attacks.'\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/hidden-character-detector/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  i18n: {\n    name: 'string:hiddenCharacterDetector.title',\n    description: 'string:hiddenCharacterDetector.description',\n    shortDescription: 'string:hiddenCharacterDetector.shortDescription',\n    longDescription: 'string:hiddenCharacterDetector.longDescription',\n    userTypes: ['developers']\n  },\n  path: 'hidden-character-detector',\n  icon: 'material-symbols:visibility-off',\n  keywords: ['unicode', 'rtl', 'override', 'security', 'invisible'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/string/hidden-character-detector/service.ts",
    "content": "import { InitialValuesType, HiddenCharacter, AnalysisResult } from './types';\n\n// RTL Override characters\nconst RTL_CHARS = [\n  { char: '\\u202E', name: 'Right-to-Left Override', unicode: 'U+202E' },\n  { char: '\\u202D', name: 'Left-to-Right Override', unicode: 'U+202D' },\n  { char: '\\u202B', name: 'Right-to-Left Embedding', unicode: 'U+202B' },\n  { char: '\\u202A', name: 'Left-to-Right Embedding', unicode: 'U+202A' },\n  { char: '\\u200F', name: 'Right-to-Left Mark', unicode: 'U+200F' },\n  { char: '\\u200E', name: 'Left-to-Right Mark', unicode: 'U+200E' }\n];\n\n// Invisible characters\nconst INVISIBLE_CHARS = [\n  { char: '\\u200B', name: 'Zero Width Space', unicode: 'U+200B' },\n  { char: '\\u200C', name: 'Zero Width Non-Joiner', unicode: 'U+200C' },\n  { char: '\\u200D', name: 'Zero Width Joiner', unicode: 'U+200D' },\n  { char: '\\u2060', name: 'Word Joiner', unicode: 'U+2060' },\n  { char: '\\uFEFF', name: 'Zero Width No-Break Space', unicode: 'U+FEFF' },\n  { char: '\\u00A0', name: 'Non-Breaking Space', unicode: 'U+00A0' },\n  { char: '\\u2000', name: 'En Quad', unicode: 'U+2000' },\n  { char: '\\u2001', name: 'Em Quad', unicode: 'U+2001' },\n  { char: '\\u2002', name: 'En Space', unicode: 'U+2002' },\n  { char: '\\u2003', name: 'Em Space', unicode: 'U+2003' },\n  { char: '\\u2004', name: 'Three-Per-Em Space', unicode: 'U+2004' },\n  { char: '\\u2005', name: 'Four-Per-Em Space', unicode: 'U+2005' },\n  { char: '\\u2006', name: 'Six-Per-Em Space', unicode: 'U+2006' },\n  { char: '\\u2007', name: 'Figure Space', unicode: 'U+2007' },\n  { char: '\\u2008', name: 'Punctuation Space', unicode: 'U+2008' },\n  { char: '\\u2009', name: 'Thin Space', unicode: 'U+2009' },\n  { char: '\\u200A', name: 'Hair Space', unicode: 'U+200A' }\n];\n\nfunction getCharacterInfo(char: string, position: number): HiddenCharacter {\n  const unicode = `U+${char\n    .charCodeAt(0)\n    .toString(16)\n    .toUpperCase()\n    .padStart(4, '0')}`;\n\n  // Check if it's an RTL character\n  const rtlChar = RTL_CHARS.find((c) => c.char === char);\n  if (rtlChar) {\n    return {\n      char,\n      unicode: rtlChar.unicode,\n      name: rtlChar.name,\n      category: 'RTL Override',\n      position,\n      isRTL: true,\n      isInvisible: false,\n      isZeroWidth: false\n    };\n  }\n\n  // Check if it's an invisible character\n  const invisibleChar = INVISIBLE_CHARS.find((c) => c.char === char);\n  if (invisibleChar) {\n    return {\n      char,\n      unicode: invisibleChar.unicode,\n      name: invisibleChar.name,\n      category: 'Invisible Character',\n      position,\n      isRTL: false,\n      isInvisible: true,\n      isZeroWidth:\n        char === '\\u200B' ||\n        char === '\\u200C' ||\n        char === '\\u200D' ||\n        char === '\\u2060' ||\n        char === '\\uFEFF'\n    };\n  }\n\n  // Check for other control characters\n  if (char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127) {\n    return {\n      char,\n      unicode,\n      name: `Control Character (${char.charCodeAt(0)})`,\n      category: 'Control Character',\n      position,\n      isRTL: false,\n      isInvisible: true,\n      isZeroWidth: false\n    };\n  }\n\n  return {\n    char,\n    unicode,\n    name: 'Regular Character',\n    category: 'Regular',\n    position,\n    isRTL: false,\n    isInvisible: false,\n    isZeroWidth: false\n  };\n}\n\nexport function analyzeHiddenCharacters(\n  text: string,\n  options: InitialValuesType\n): AnalysisResult {\n  const hiddenCharacters: HiddenCharacter[] = [];\n\n  for (let i = 0; i < text.length; i++) {\n    const char = text[i];\n    const charInfo = getCharacterInfo(char, i);\n\n    // Filter based on options\n    if (options.highlightRTL && charInfo.isRTL) {\n      hiddenCharacters.push(charInfo);\n    } else if (options.showInvisibleChars && charInfo.isInvisible) {\n      hiddenCharacters.push(charInfo);\n    } else if (options.includeZeroWidthChars && charInfo.isZeroWidth) {\n      hiddenCharacters.push(charInfo);\n    }\n  }\n\n  const hasRTLOverride = hiddenCharacters.some((c) => c.isRTL);\n  const hasInvisibleChars = hiddenCharacters.some((c) => c.isInvisible);\n  const hasZeroWidthChars = hiddenCharacters.some((c) => c.isZeroWidth);\n\n  return {\n    originalText: text,\n    hiddenCharacters,\n    hasRTLOverride,\n    hasInvisibleChars,\n    hasZeroWidthChars,\n    totalHiddenChars: hiddenCharacters.length\n  };\n}\n\nexport function main(input: string, options: InitialValuesType): string {\n  const result = analyzeHiddenCharacters(input, options);\n\n  if (result.totalHiddenChars === 0) {\n    return 'No hidden characters detected in the text.';\n  }\n\n  let output = `Found ${result.totalHiddenChars} hidden character(s):\\n\\n`;\n\n  result.hiddenCharacters.forEach((char) => {\n    output += `Position ${char.position}: ${char.name} (${char.unicode})\\n`;\n    if (options.showUnicodeCodes) {\n      output += `  Unicode: ${char.unicode}\\n`;\n    }\n    output += `  Category: ${char.category}\\n`;\n    if (char.isRTL) output += `  ⚠️  RTL Override Character\\n`;\n    if (char.isInvisible) output += `  👁️  Invisible Character\\n`;\n    if (char.isZeroWidth) output += `  📏  Zero Width Character\\n`;\n    output += '\\n';\n  });\n\n  if (result.hasRTLOverride) {\n    output +=\n      '⚠️  WARNING: RTL Override characters detected! This could be used in attacks.\\n';\n  }\n\n  return output;\n}\n"
  },
  {
    "path": "src/pages/tools/string/hidden-character-detector/types.ts",
    "content": "export type InitialValuesType = {\n  showUnicodeCodes: boolean;\n  highlightRTL: boolean;\n  showInvisibleChars: boolean;\n  includeZeroWidthChars: boolean;\n};\n\nexport interface HiddenCharacter {\n  char: string;\n  unicode: string;\n  name: string;\n  category: string;\n  position: number;\n  isRTL: boolean;\n  isInvisible: boolean;\n  isZeroWidth: boolean;\n}\n\nexport interface AnalysisResult {\n  originalText: string;\n  hiddenCharacters: HiddenCharacter[];\n  hasRTLOverride: boolean;\n  hasInvisibleChars: boolean;\n  hasZeroWidthChars: boolean;\n  totalHiddenChars: number;\n}\n"
  },
  {
    "path": "src/pages/tools/string/index.ts",
    "content": "import { tool as stringHiddenCharacterDetector } from './hidden-character-detector/meta';\nimport { tool as stringRemoveDuplicateLines } from './remove-duplicate-lines/meta';\nimport { tool as stringRotate } from './rotate/meta';\nimport { tool as stringQuote } from './quote/meta';\nimport { tool as stringRot13 } from './rot13/meta';\nimport { tool as stringReverse } from './reverse/meta';\nimport { tool as stringRandomizeCase } from './randomize-case/meta';\nimport { tool as stringUppercase } from './uppercase/meta';\nimport { tool as stringExtractSubstring } from './extract-substring/meta';\nimport { tool as stringCreatePalindrome } from './create-palindrome/meta';\nimport { tool as stringPalindrome } from './palindrome/meta';\nimport { tool as stringToMorse } from './to-morse/meta';\nimport { tool as stringSplit } from './split/meta';\nimport { tool as stringJoin } from './join/meta';\nimport { tool as stringReplace } from './text-replacer/meta';\nimport { tool as stringRepeat } from './repeat/meta';\nimport { tool as stringTruncate } from './truncate/meta';\nimport { tool as stringBase64 } from './base64/meta';\nimport { tool as stringStatistic } from './statistic/meta';\nimport { tool as stringCensor } from './censor/meta';\nimport { tool as stringPasswordGenerator } from './password-generator/meta';\nimport { tool as stringEncodeUrl } from './url-encode/meta';\nimport { tool as StringDecodeUrl } from './url-decode/meta';\nimport { tool as stringUnicode } from './unicode/meta';\n\nexport const stringTools = [\n  stringSplit,\n  stringJoin,\n  stringRemoveDuplicateLines,\n  stringToMorse,\n  stringReplace,\n  stringRepeat,\n  stringTruncate,\n  stringReverse,\n  stringRandomizeCase,\n  stringUppercase,\n  stringExtractSubstring,\n  stringCreatePalindrome,\n  stringPalindrome,\n  stringQuote,\n  stringRotate,\n  stringRot13,\n  stringBase64,\n  stringStatistic,\n  stringCensor,\n  stringPasswordGenerator,\n  stringEncodeUrl,\n  StringDecodeUrl,\n  stringUnicode,\n  stringHiddenCharacterDetector\n];\n"
  },
  {
    "path": "src/pages/tools/string/join/index.tsx",
    "content": "import React, { useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport ToolContent from '@components/ToolContent';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { mergeText } from './service';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  joinCharacter: '',\n  deleteBlank: true,\n  deleteTrailing: true\n};\ntype InitialValuesType = typeof initialValues;\nconst validationSchema = Yup.object().shape({\n  joinCharacter: Yup.string().required('Join character is required'),\n  deleteBlank: Yup.boolean().required('Delete blank is required'),\n  deleteTrailing: Yup.boolean().required('Delete trailing is required')\n});\n\nconst mergeOptions = {\n  placeholder: 'Join Character',\n  description:\n    'Symbol that connects broken\\n' + 'pieces of text. (Space by default.)\\n',\n  accessor: 'joinCharacter' as keyof InitialValuesType\n};\n\nconst blankTrailingOptions: {\n  title: string;\n  description: string;\n  accessor: keyof Omit<InitialValuesType, 'joinCharacter'>;\n}[] = [\n  {\n    title: 'Delete Blank Lines',\n    description: \"Delete lines that don't have\\n text symbols.\\n\",\n    accessor: 'deleteBlank'\n  },\n  {\n    title: 'Delete Trailing Spaces',\n    description: 'Remove spaces and tabs at\\n the end of the lines.\\n',\n    accessor: 'deleteTrailing'\n  }\n];\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Merge a To-Do List',\n    description:\n      \"In this example, we merge a bullet point list into one sentence, separating each item by the word 'and'. We also remove all empty lines and trailing spaces. If we didn't remove the empty lines, then they'd be joined with the separator word, making the separator word appear multiple times. If we didn't remove the trailing tabs and spaces, then they'd create extra spacing in the joined text and it wouldn't look nice.\",\n    sampleText: `clean the house\n\ngo shopping\nfeed the cat\n\nmake dinner\nbuild a rocket ship and fly away`,\n    sampleResult: `clean the house and go shopping and feed the cat and make dinner and build a rocket ship and fly away`,\n    sampleOptions: {\n      joinCharacter: 'and',\n      deleteBlank: true,\n      deleteTrailing: true\n    }\n  },\n  {\n    title: 'Comma Separated List',\n    description:\n      'This example joins a column of words into a comma separated list of words.',\n    sampleText: `computer\nmemory\nprocessor\nmouse\nkeyboard`,\n    sampleResult: `computer, memory, processor, mouse, keyboard`,\n    sampleOptions: {\n      joinCharacter: ',',\n      deleteBlank: false,\n      deleteTrailing: false\n    }\n  },\n  {\n    title: 'Vertical Word to Horizontal',\n    description:\n      'This example rotates words from a vertical position to horizontal. An empty separator is used for this purpose.',\n    sampleText: `T\ne\nx\nt\na\nb\nu\nl\no\nu\ns\n!`,\n    sampleResult: `Textabulous!`,\n    sampleOptions: {\n      joinCharacter: '',\n      deleteBlank: false,\n      deleteTrailing: false\n    }\n  }\n];\n\nexport default function JoinText({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n  const compute = (optionsValues: InitialValuesType, input: any) => {\n    const { joinCharacter, deleteBlank, deleteTrailing } = optionsValues;\n    setResult(mergeText(input, deleteBlank, deleteTrailing, joinCharacter));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('join.textMergedOptions'),\n      component: (\n        <TextFieldWithDesc\n          placeholder={t('join.joinCharacterPlaceholder')}\n          value={values['joinCharacter']}\n          onOwnChange={(value) => updateField(mergeOptions.accessor, value)}\n          description={t('join.joinCharacterDescription')}\n        />\n      )\n    },\n    {\n      title: t('join.blankLinesAndTrailingSpaces'),\n      component: blankTrailingOptions.map((option) => (\n        <CheckboxWithDesc\n          key={option.accessor}\n          title={t(`join.${option.accessor}Title`)}\n          checked={!!values[option.accessor]}\n          onChange={(value) => updateField(option.accessor, value)}\n          description={t(`join.${option.accessor}Description`)}\n        />\n      ))\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('join.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('join.resultTitle')} value={result} />\n      }\n      getGroups={getGroups}\n      toolInfo={{\n        title: t('join.toolInfo.title'),\n        description: t('join.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/join/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'join',\n\n  icon: 'material-symbols-light:join',\n\n  keywords: ['text', 'join'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:join.title',\n    description: 'string:join.description',\n    shortDescription: 'string:join.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/join/service.ts",
    "content": "export function mergeText(\n  text: string,\n  deleteBlankLines: boolean = true,\n  deleteTrailingSpaces: boolean = true,\n  joinCharacter: string = ''\n): string {\n  let processedLines: string[] = text.split('\\n');\n  if (deleteTrailingSpaces) {\n    processedLines = processedLines.map((line) => line.trimEnd());\n  }\n\n  if (deleteBlankLines) {\n    processedLines = processedLines.filter((line) => line.trim());\n  }\n\n  return processedLines.join(joinCharacter);\n}\n"
  },
  {
    "path": "src/pages/tools/string/join/string-join.e2e.spec.ts",
    "content": "import { expect, test } from '@playwright/test';\n\ntest.describe('JoinText Component', () => {\n  test.beforeEach(async ({ page }) => {\n    await page.goto('/string/join');\n  });\n\n  test('should merge text pieces with specified join character', async ({\n    page\n  }) => {\n    // Input the text pieces\n    await page.getByTestId('text-input').fill('1\\n2');\n\n    const result = await page.getByTestId('text-result').inputValue();\n\n    expect(result).toBe('12');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/join/string-join.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { mergeText } from './service';\n\ndescribe('mergeText', () => {\n  it('should merge lines with default settings (delete blank lines, delete trailing spaces, join with empty string)', () => {\n    const input = 'line1  \\n  \\nline2\\nline3  \\n\\nline4';\n    const expected = 'line1line2line3line4';\n    expect(mergeText(input)).toBe(expected);\n  });\n\n  it('should merge lines and preserve blank lines when deleteBlankLines is false', () => {\n    const input = 'line1  \\n  \\nline2\\nline3  \\n\\nline4';\n    const expected = 'line1line2line3line4';\n    expect(mergeText(input, false, true, '')).toBe(expected);\n  });\n\n  it('should merge lines and preserve trailing spaces when deleteTrailingSpaces is false', () => {\n    const input = 'line1  \\n  \\nline2\\nline3  \\n\\nline4';\n    const expected = 'line1  line2line3  line4';\n    expect(mergeText(input, true, false)).toBe(expected);\n  });\n\n  it('should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false', () => {\n    const input = 'line1  \\n  \\nline2\\nline3  \\n\\nline4';\n    const expected = 'line1    line2line3  line4';\n    expect(mergeText(input, false, false)).toBe(expected);\n  });\n\n  it('should merge lines with a specified joinCharacter', () => {\n    const input = 'line1  \\n  \\nline2\\nline3  \\n\\nline4';\n    const expected = 'line1 line2 line3 line4';\n    expect(mergeText(input, true, true, ' ')).toBe(expected);\n  });\n\n  it('should handle empty input', () => {\n    const input = '';\n    const expected = '';\n    expect(mergeText(input)).toBe(expected);\n  });\n\n  it('should handle input with only blank lines', () => {\n    const input = '  \\n  \\n\\n';\n    const expected = '';\n    expect(mergeText(input)).toBe(expected);\n  });\n\n  it('should handle input with only trailing spaces', () => {\n    const input = 'line1  \\nline2  \\nline3  ';\n    const expected = 'line1line2line3';\n    expect(mergeText(input)).toBe(expected);\n  });\n\n  it('should handle single line input', () => {\n    const input = 'single line';\n    const expected = 'single line';\n    expect(mergeText(input)).toBe(expected);\n  });\n\n  it('should join lines with new line character when joinCharacter is set to \"\\\\n\"', () => {\n    const input = 'line1  \\n  \\nline2\\nline3  \\n\\nline4';\n    const expected = 'line1\\nline2\\nline3\\nline4';\n    expect(mergeText(input, true, true, '\\n')).toBe(expected);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/palindrome/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState, useRef } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport ToolOptions, { GetGroupsType } from '@components/options/ToolOptions';\nimport { palindromeList, SplitOperatorType } from './service';\nimport RadioWithTextField from '@components/options/RadioWithTextField';\nimport ToolInputAndResult from '@components/ToolInputAndResult';\nimport ToolExamples, {\n  CardExampleType\n} from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { FormikProps } from 'formik';\nimport ToolContent from '@components/ToolContent';\n\nconst initialValues = {\n  splitOperatorType: 'symbol' as SplitOperatorType,\n  symbolValue: ' ',\n  regexValue: '\\\\s+'\n};\n\nconst splitOperators: {\n  title: string;\n  description: string;\n  type: SplitOperatorType;\n}[] = [\n  {\n    title: 'Use a Symbol for Splitting',\n    description:\n      'Character that will be used to split text into parts for palindrome checking.',\n    type: 'symbol'\n  },\n  {\n    title: 'Use a Regex for Splitting',\n    type: 'regex',\n    description:\n      'Regular expression that will be used to split text into parts for palindrome checking.'\n  }\n];\n\nconst exampleCards: CardExampleType<typeof initialValues>[] = [\n  {\n    title: 'Check for Word Palindromes',\n    description:\n      'Checks if each word in the text is a palindrome. Returns \"true\" for palindromes and \"false\" for non-palindromes.',\n    sampleText: 'radar level hello anna',\n    sampleResult: 'true true false true',\n    sampleOptions: {\n      ...initialValues,\n      symbolValue: ' '\n    }\n  },\n  {\n    title: 'Check CSV Words',\n    description: 'Checks palindrome status for comma-separated words.',\n    sampleText: 'mom,dad,wow,test',\n    sampleResult: 'true true true false',\n    sampleOptions: {\n      ...initialValues,\n      symbolValue: ','\n    }\n  },\n  {\n    title: 'Check with Regular Expression',\n    description:\n      'Use a regular expression to split text and check for palindromes.',\n    sampleText: 'level:madam;noon|test',\n    sampleResult: 'true true true false',\n    sampleOptions: {\n      ...initialValues,\n      splitOperatorType: 'regex',\n      regexValue: '[:|;]|\\\\|'\n    }\n  }\n];\n\nexport default function Palindrome({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const computeExternal = (\n    optionsValues: typeof initialValues,\n    input: string\n  ) => {\n    const { splitOperatorType, symbolValue, regexValue } = optionsValues;\n    const separator = splitOperatorType === 'symbol' ? symbolValue : regexValue;\n    setResult(palindromeList(splitOperatorType, input, separator));\n  };\n\n  const getGroups: GetGroupsType<typeof initialValues> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Splitting options',\n      component: splitOperators.map(({ title, description, type }) => (\n        <RadioWithTextField\n          key={type}\n          checked={type === values.splitOperatorType}\n          title={title}\n          fieldName={'splitOperatorType'}\n          description={description}\n          value={values[`${type}Value`]}\n          onRadioClick={() => updateField('splitOperatorType', type)}\n          onTextChange={(val) => updateField(`${type}Value`, val)}\n        />\n      ))\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={computeExternal}\n      input={input}\n      setInput={setInput}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={\n        <ToolTextResult title={'Palindrome results'} value={result} />\n      }\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/palindrome/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('string', {\n  path: 'palindrome',\n  icon: 'material-symbols-light:search',\n\n  keywords: ['palindrome'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:palindrome.title',\n    description: 'string:palindrome.description',\n    shortDescription: 'string:palindrome.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/palindrome/palindrome.service.test.ts",
    "content": "import { describe, expect } from 'vitest';\nimport { palindromeList } from './service';\n\ndescribe('palindromeList', () => {\n  test('should return true for single character words', () => {\n    const input = 'a|b|c';\n    const separator = '|';\n    const result = palindromeList('symbol', input, separator);\n    expect(result).toBe('true|true|true');\n  });\n\n  test('should return false for non-palindromes', () => {\n    const input = 'hello|world';\n    const separator = '|';\n    const result = palindromeList('symbol', input, separator);\n    expect(result).toBe('false|false');\n  });\n\n  test('should split using regex', () => {\n    const input = 'racecar,abba,hello';\n    const separator = ',';\n    const result = palindromeList('regex', input, separator);\n    expect(result).toBe('true,true,false');\n  });\n\n  test('should return empty string for empty input', () => {\n    const input = '';\n    const separator = '|';\n    const result = palindromeList('symbol', input, separator);\n    expect(result).toBe('');\n  });\n\n  test('should split using custom separator', () => {\n    const input = 'racecar;abba;hello';\n    const separator = ';';\n    const result = palindromeList('symbol', input, separator);\n    expect(result).toBe('true;true;false');\n  });\n\n  test('should handle leading and trailing spaces', () => {\n    const input = ' racecar | abba | hello ';\n    const separator = '|';\n    const result = palindromeList('symbol', input, separator);\n    expect(result).toBe('true|true|false');\n  });\n\n  test('should handle multilines checking with trimming', () => {\n    const input = ' racecar  \\n    abba \\n  hello ';\n    const separator = '\\n';\n    const result = palindromeList('symbol', input, separator);\n    expect(result).toBe('true\\ntrue\\nfalse');\n  });\n\n  test('should handle empty strings in input', () => {\n    const input = 'racecar||hello';\n    const separator = '|';\n    const result = palindromeList('symbol', input, separator);\n    expect(result).toBe('true|true|false');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/palindrome/service.ts",
    "content": "export type SplitOperatorType = 'symbol' | 'regex';\n\nfunction isPalindrome(word: string, left: number, right: number): boolean {\n  if (left >= right) return true;\n  if (word[left] !== word[right]) return false;\n\n  return isPalindrome(word, left + 1, right - 1);\n}\n\n// check each word of the input and add the palindrome status in an array\nfunction checkPalindromes(array: string[]): boolean[] {\n  const status: boolean[] = [];\n  for (const word of array) {\n    const palindromeStatus = isPalindrome(word, 0, word.length - 1);\n    status.push(palindromeStatus);\n  }\n  return status;\n}\n\nexport function palindromeList(\n  splitOperatorType: SplitOperatorType,\n  input: string,\n  separator: string // the splitting separator will be the joining separator for visual satisfaction\n): string {\n  if (!input) return '';\n  let array: string[];\n  switch (splitOperatorType) {\n    case 'symbol':\n      array = input.split(separator);\n      break;\n    case 'regex':\n      array = input.split(new RegExp(separator));\n      break;\n  }\n  // trim all items to focus on the word and not biasing the result due to spaces (leading and trailing)\n  array = array.map((item) => item.trim());\n\n  const statusArray = checkPalindromes(array);\n\n  return statusArray.map((status) => status.toString()).join(separator);\n}\n"
  },
  {
    "path": "src/pages/tools/string/password-generator/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Box, Checkbox, FormControlLabel, FormGroup } from '@mui/material';\nimport { generatePassword } from './service';\nimport { initialValues, InitialValuesType } from './initialValues';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { useTranslation } from 'react-i18next';\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Strong Password (12 characters)',\n    description:\n      'Generate a secure password with all character types including symbols.',\n    sampleText: '',\n    sampleResult: 'A7#mK9$pL2@x',\n    sampleOptions: {\n      length: '12',\n      includeLowercase: true,\n      includeUppercase: true,\n      includeNumbers: true,\n      includeSymbols: true,\n      avoidAmbiguous: false\n    }\n  },\n  {\n    title: 'Simple Password (8 characters)',\n    description: 'Generate a basic password with letters and numbers only.',\n    sampleText: '',\n    sampleResult: 'Ab3mK9pL',\n    sampleOptions: {\n      length: '8',\n      includeLowercase: true,\n      includeUppercase: true,\n      includeNumbers: true,\n      includeSymbols: false,\n      avoidAmbiguous: false\n    }\n  },\n  {\n    title: 'Clear Password (No ambiguous)',\n    description:\n      'Generate a password without ambiguous characters (i, I, l, 0, O).',\n    sampleText: '',\n    sampleResult: 'A7#mK9$pL2@x',\n    sampleOptions: {\n      length: '12',\n      includeLowercase: true,\n      includeUppercase: true,\n      includeNumbers: true,\n      includeSymbols: true,\n      avoidAmbiguous: true\n    }\n  }\n];\n\nexport default function PasswordGenerator({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [result, setResult] = useState<string>('');\n\n  function compute(values: InitialValuesType) {\n    setResult(generatePassword(values));\n  }\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('passwordGenerator.optionsTitle'),\n      component: (\n        <Box sx={{ display: 'flex', gap: 2, flexDirection: 'column' }}>\n          <TextFieldWithDesc\n            description={t('passwordGenerator.lengthDesc')}\n            placeholder={t('passwordGenerator.lengthPlaceholder')}\n            value={values.length}\n            onOwnChange={(val) => updateField('length', val)}\n            type=\"number\"\n          />\n\n          <FormGroup>\n            <FormControlLabel\n              control={\n                <Checkbox\n                  checked={values.includeLowercase}\n                  onChange={(e) =>\n                    updateField('includeLowercase', e.target.checked)\n                  }\n                />\n              }\n              label={t('passwordGenerator.includeLowercase')}\n            />\n            <FormControlLabel\n              control={\n                <Checkbox\n                  checked={values.includeUppercase}\n                  onChange={(e) =>\n                    updateField('includeUppercase', e.target.checked)\n                  }\n                />\n              }\n              label={t('passwordGenerator.includeUppercase')}\n            />\n            <FormControlLabel\n              control={\n                <Checkbox\n                  checked={values.includeNumbers}\n                  onChange={(e) =>\n                    updateField('includeNumbers', e.target.checked)\n                  }\n                />\n              }\n              label={t('passwordGenerator.includeNumbers')}\n            />\n            <FormControlLabel\n              control={\n                <Checkbox\n                  checked={values.includeSymbols}\n                  onChange={(e) =>\n                    updateField('includeSymbols', e.target.checked)\n                  }\n                />\n              }\n              label={t('passwordGenerator.includeSymbols')}\n            />\n            <FormControlLabel\n              control={\n                <Checkbox\n                  checked={values.avoidAmbiguous}\n                  onChange={(e) =>\n                    updateField('avoidAmbiguous', e.target.checked)\n                  }\n                />\n              }\n              label={t('passwordGenerator.avoidAmbiguous')}\n            />\n          </FormGroup>\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      resultComponent={\n        <ToolTextResult\n          title={t('passwordGenerator.resultTitle')}\n          value={result}\n        />\n      }\n      toolInfo={{\n        title: t('passwordGenerator.toolInfo.title'),\n        description: t('passwordGenerator.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/password-generator/initialValues.ts",
    "content": "export type InitialValuesType = {\n  length: string; // user enters a number here\n  includeLowercase: boolean;\n  includeUppercase: boolean;\n  includeNumbers: boolean;\n  includeSymbols: boolean;\n  avoidAmbiguous: boolean;\n};\n\nexport const initialValues: InitialValuesType = {\n  length: '12',\n  includeLowercase: true,\n  includeUppercase: true,\n  includeNumbers: true,\n  includeSymbols: true,\n  avoidAmbiguous: false\n};\n"
  },
  {
    "path": "src/pages/tools/string/password-generator/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'password-generator',\n  icon: 'material-symbols:key',\n  keywords: ['password', 'generator', 'random', 'secure'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:passwordGenerator.title',\n    description: 'string:passwordGenerator.description',\n    shortDescription: 'string:passwordGenerator.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/password-generator/password-generator.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { generatePassword } from './service';\nimport { initialValues } from './initialValues';\n\ndescribe('generatePassword', () => {\n  it('should generate a password with the specified length', () => {\n    const options = { ...initialValues, length: '10' };\n    const result = generatePassword(options);\n    expect(result).toHaveLength(10);\n  });\n\n  it('should return empty string for invalid length', () => {\n    const options = { ...initialValues, length: '0' };\n    const result = generatePassword(options);\n    expect(result).toBe('');\n  });\n\n  it('should return empty string for non-numeric length', () => {\n    const options = { ...initialValues, length: 'abc' };\n    const result = generatePassword(options);\n    expect(result).toBe('');\n  });\n\n  it('should return empty string when no character types are selected', () => {\n    const options = {\n      ...initialValues,\n      includeLowercase: false,\n      includeUppercase: false,\n      includeNumbers: false,\n      includeSymbols: false\n    };\n    const result = generatePassword(options);\n    expect(result).toBe('');\n  });\n\n  it('should only include lowercase letters when only lowercase is selected', () => {\n    const options = {\n      ...initialValues,\n      length: '20',\n      includeLowercase: true,\n      includeUppercase: false,\n      includeNumbers: false,\n      includeSymbols: false\n    };\n    const result = generatePassword(options);\n    expect(result).toMatch(/^[a-z]+$/);\n    expect(result).toHaveLength(20);\n  });\n\n  it('should only include uppercase letters when only uppercase is selected', () => {\n    const options = {\n      ...initialValues,\n      length: '15',\n      includeLowercase: false,\n      includeUppercase: true,\n      includeNumbers: false,\n      includeSymbols: false\n    };\n    const result = generatePassword(options);\n    expect(result).toMatch(/^[A-Z]+$/);\n    expect(result).toHaveLength(15);\n  });\n\n  it('should only include numbers when only numbers is selected', () => {\n    const options = {\n      ...initialValues,\n      length: '8',\n      includeLowercase: false,\n      includeUppercase: false,\n      includeNumbers: true,\n      includeSymbols: false\n    };\n    const result = generatePassword(options);\n    expect(result).toMatch(/^[0-9]+$/);\n    expect(result).toHaveLength(8);\n  });\n\n  it('should include mixed character types when multiple are selected', () => {\n    const options = {\n      ...initialValues,\n      length: '100', // larger sample for better testing\n      includeLowercase: true,\n      includeUppercase: true,\n      includeNumbers: true,\n      includeSymbols: false\n    };\n    const result = generatePassword(options);\n    expect(result).toMatch(/^[a-zA-Z0-9]+$/);\n    expect(result).toHaveLength(100);\n  });\n\n  it('should exclude ambiguous characters when avoidAmbiguous is true', () => {\n    const options = {\n      ...initialValues,\n      length: '50',\n      avoidAmbiguous: true\n    };\n    const result = generatePassword(options);\n    expect(result).not.toMatch(/[iIl0O]/);\n    expect(result).toHaveLength(50);\n  });\n\n  it('should include symbols when includeSymbols is true', () => {\n    const options = {\n      ...initialValues,\n      length: '30',\n      includeLowercase: false,\n      includeUppercase: false,\n      includeNumbers: false,\n      includeSymbols: true\n    };\n    const result = generatePassword(options);\n    expect(result).toMatch(/^[!@#$%^&*()_+~`|}{[\\]:;?><,./\\-=]+$/);\n    expect(result).toHaveLength(30);\n  });\n\n  it('should exclude ambiguous characters from symbols too', () => {\n    const options = {\n      ...initialValues,\n      length: '50',\n      includeLowercase: false,\n      includeUppercase: false,\n      includeNumbers: true,\n      includeSymbols: true,\n      avoidAmbiguous: true\n    };\n    const result = generatePassword(options);\n    expect(result).not.toMatch(/[iIl0O]/);\n    expect(result).toHaveLength(50);\n  });\n\n  it('should handle edge case with very short length', () => {\n    const options = { ...initialValues, length: '1' };\n    const result = generatePassword(options);\n    expect(result).toHaveLength(1);\n  });\n\n  it('should handle negative length', () => {\n    const options = { ...initialValues, length: '-5' };\n    const result = generatePassword(options);\n    expect(result).toBe('');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/password-generator/service.ts",
    "content": "import type { InitialValuesType } from './initialValues';\n\nexport function generatePassword(options: InitialValuesType): string {\n  const length = parseInt(options.length || '', 10);\n  if (isNaN(length) || length <= 0) {\n    return '';\n  }\n\n  let charset = '';\n  const lower = 'abcdefghijklmnopqrstuvwxyz';\n  const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';\n  const numbers = '0123456789';\n  const symbols = '!@#$%^&*()_+~`|}{[]:;?><,./-=';\n\n  if (options.includeLowercase) charset += lower;\n  if (options.includeUppercase) charset += upper;\n  if (options.includeNumbers) charset += numbers;\n  if (options.includeSymbols) charset += symbols;\n\n  if (options.avoidAmbiguous) {\n    // ambiguous set = i, I, l, 0, O\n    const ambig = new Set(['i', 'I', 'l', '0', 'O']);\n    charset = Array.from(charset)\n      .filter((c) => !ambig.has(c))\n      .join('');\n  }\n\n  if (!charset) {\n    return ''; // nothing to pick from\n  }\n\n  let pwd = '';\n  for (let i = 0; i < length; i++) {\n    const idx = Math.floor(Math.random() * charset.length);\n    pwd += charset[idx];\n  }\n  return pwd;\n}\n"
  },
  {
    "path": "src/pages/tools/string/quote/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Box } from '@mui/material';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { stringQuoter } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { useTranslation } from 'react-i18next';\n\ninterface InitialValuesType {\n  leftQuote: string;\n  rightQuote: string;\n  doubleQuotation: boolean;\n  emptyQuoting: boolean;\n  multiLine: boolean;\n}\n\nconst initialValues: InitialValuesType = {\n  leftQuote: '\"',\n  rightQuote: '\"',\n  doubleQuotation: false,\n  emptyQuoting: true,\n  multiLine: true\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Quote text with double quotes',\n    description: 'This example shows how to quote text with double quotes.',\n    sampleText: 'Hello World',\n    sampleResult: '\"Hello World\"',\n    sampleOptions: {\n      leftQuote: '\"',\n      rightQuote: '\"',\n      doubleQuotation: false,\n      emptyQuoting: true,\n      multiLine: false\n    }\n  },\n  {\n    title: 'Quote multi-line text with single quotes',\n    description:\n      'This example shows how to quote multi-line text with single quotes.',\n    sampleText: 'Hello\\nWorld',\n    sampleResult: \"'Hello'\\n'World'\",\n    sampleOptions: {\n      leftQuote: \"'\",\n      rightQuote: \"'\",\n      doubleQuotation: false,\n      emptyQuoting: true,\n      multiLine: true\n    }\n  },\n  {\n    title: 'Quote with custom quotes',\n    description: 'This example shows how to quote text with custom quotes.',\n    sampleText: 'Hello World',\n    sampleResult: '<<Hello World>>',\n    sampleOptions: {\n      leftQuote: '<<',\n      rightQuote: '>>',\n      doubleQuotation: false,\n      emptyQuoting: true,\n      multiLine: false\n    }\n  }\n];\n\nexport default function Quote({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: string) => {\n    if (input) {\n      setResult(\n        stringQuoter(\n          input,\n          optionsValues.leftQuote,\n          optionsValues.rightQuote,\n          optionsValues.doubleQuotation,\n          optionsValues.emptyQuoting,\n          optionsValues.multiLine\n        )\n      );\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('quote.quoteOptions'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.leftQuote}\n            onOwnChange={(val) => updateField('leftQuote', val)}\n            description={t('quote.leftQuoteDescription')}\n          />\n          <TextFieldWithDesc\n            value={values.rightQuote}\n            onOwnChange={(val) => updateField('rightQuote', val)}\n            description={t('quote.rightQuoteDescription')}\n          />\n          <CheckboxWithDesc\n            checked={values.doubleQuotation}\n            onChange={(checked) => updateField('doubleQuotation', checked)}\n            title={t('quote.allowDoubleQuotation')}\n          />\n          <CheckboxWithDesc\n            checked={values.emptyQuoting}\n            onChange={(checked) => updateField('emptyQuoting', checked)}\n            title={t('quote.quoteEmptyLines')}\n          />\n          <CheckboxWithDesc\n            checked={values.multiLine}\n            onChange={(checked) => updateField('multiLine', checked)}\n            title={t('quote.processAsMultiLine')}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolTextInput\n          title={t('quote.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('quote.resultTitle')} value={result} />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      toolInfo={{\n        title: t('quote.toolInfo.title'),\n        description: t('quote.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n      input={input}\n      setInput={setInput}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/quote/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('string', {\n  path: 'quote',\n  icon: 'material-symbols-light:format-quote',\n\n  keywords: ['quote'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:quote.title',\n    description: 'string:quote.description',\n    shortDescription: 'string:quote.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/quote/quote.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { quote, stringQuoter } from './service';\n\ndescribe('quote function', () => {\n  it('quotes a word with single quotes', () => {\n    expect(quote('Hello', \"'\", \"'\", false)).toBe(\"'Hello'\");\n  });\n\n  it('quotes a word with double quotes', () => {\n    expect(quote('World', '\"', '\"', true)).toBe('\"World\"');\n  });\n\n  it('does not re-quote already quoted word', () => {\n    expect(quote('\"Goodbye\"', '\"', '\"', false)).toBe('\"Goodbye\"');\n  });\n\n  it('handles empty word when emptyQuoting is true', () => {\n    expect(quote('', \"'\", \"'\", false)).toBe(\"''\");\n  });\n\n  it('handles empty word when emptyQuoting is false', () => {\n    expect(quote('', \"'\", \"'\", false)).toBe(\"''\"); // Replace with expected behavior\n  });\n});\n\ndescribe('stringQuoter function', () => {\n  it('quotes a multi-line input with single quotes', () => {\n    const input = 'Hello\\nWorld\\n';\n    const expected = \"'Hello'\\n'World'\\n''\";\n    expect(stringQuoter(input, \"'\", \"'\", false, true, true)).toBe(expected);\n  });\n\n  it('handles empty lines when emptyQuoting is true', () => {\n    const input = 'Hello\\n\\nWorld';\n    const expected = \"'Hello'\\n''\\n'World'\";\n    expect(stringQuoter(input, \"'\", \"'\", false, true, true)).toBe(expected);\n  });\n\n  it('does not quote empty lines when emptyQuoting is false', () => {\n    const input = 'Hello\\n\\nWorld';\n    const expected = \"'Hello'\\n\\n'World'\";\n    expect(stringQuoter(input, \"'\", \"'\", false, false, true)).toBe(expected);\n  });\n\n  it('quotes a single-line input with double quotes', () => {\n    const input = 'Hello';\n    const expected = '\"Hello\"';\n    expect(stringQuoter(input, '\"', '\"', true, true, false)).toBe(expected);\n  });\n\n  it('handles empty input', () => {\n    const input = '';\n    const expected = '';\n    expect(stringQuoter(input, \"'\", \"'\", false, true, false)).toBe(expected);\n  });\n\n  it('handles spaces input', () => {\n    const input = ' ';\n    const expected = \"' '\";\n    expect(stringQuoter(input, \"'\", \"'\", false, true, false)).toBe(expected);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/quote/service.ts",
    "content": "export function quote(\n  word: string,\n  leftQuote: string,\n  rightQuote: string,\n  doubleQuotation: boolean\n): string {\n  const array: string[] = word.split('');\n\n  // Check if double quotation is enabled and adjust accordingly\n  if (doubleQuotation) {\n    array.unshift(leftQuote);\n    array.push(rightQuote);\n  } else {\n    // Check if the word is already quoted correctly\n    if (array[0] === leftQuote && array[array.length - 1] === rightQuote) {\n      return word;\n    }\n\n    // Append quotes if not already quoted\n    array.unshift(leftQuote);\n    array.push(rightQuote);\n  }\n\n  return array.join('');\n}\n\nexport function stringQuoter(\n  input: string,\n  leftQuote: string,\n  rightQuote: string,\n  doubleQuotation: boolean,\n  emptyQuoting: boolean,\n  multiLine: boolean\n) {\n  if (!input) {\n    return '';\n  }\n  let arrayOfString: string[] = [];\n  const result: string[] = [];\n  if (multiLine) {\n    arrayOfString = input.split('\\n');\n  } else {\n    arrayOfString.push(input);\n  }\n  for (const word of arrayOfString) {\n    if (word === '') {\n      if (emptyQuoting) {\n        result.push(quote(word, leftQuote, rightQuote, doubleQuotation));\n      } else {\n        result.push(word);\n      }\n    } else {\n      result.push(quote(word, leftQuote, rightQuote, doubleQuotation));\n    }\n  }\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/string/randomize-case/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { randomizeCase } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolContent from '@components/ToolContent';\n\nconst initialValues = {};\n\nconst exampleCards: CardExampleType<typeof initialValues>[] = [\n  {\n    title: 'Randomize Text Case',\n    description:\n      'This example turns normal text into a random mix of uppercase and lowercase letters.',\n    sampleText: 'The quick brown fox jumps over the lazy dog.',\n    sampleResult: 'tHe qUIcK BrOWn fOx JuMPs ovEr ThE LaZy Dog.',\n    sampleOptions: {}\n  },\n  {\n    title: 'Randomize Code Case',\n    description:\n      'Transform code identifiers with randomized case for a chaotic look.',\n    sampleText:\n      'function calculateTotal(price, quantity) { return price * quantity; }',\n    sampleResult:\n      'FuNcTIon cAlCuLAtEtOtaL(pRicE, qUaNTiTy) { rETuRn PrICe * QuAnTiTY; }',\n    sampleOptions: {}\n  },\n  {\n    title: 'Randomize a Famous Quote',\n    description:\n      'Give a unique randomized case treatment to a well-known quote.',\n    sampleText: 'To be or not to be, that is the question.',\n    sampleResult: 'tO Be oR NoT To bE, ThAt iS ThE QueStIoN.',\n    sampleOptions: {}\n  }\n];\n\nexport default function RandomizeCase({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const computeExternal = (\n    _optionsValues: typeof initialValues,\n    input: string\n  ) => {\n    setResult(randomizeCase(input));\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={null}\n      compute={computeExternal}\n      input={input}\n      setInput={setInput}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={\n        <ToolTextResult title={'Randomized text'} value={result} />\n      }\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/randomize-case/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('string', {\n  path: 'randomize-case',\n  icon: 'material-symbols-light:shuffle',\n\n  keywords: ['randomize', 'case'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:randomizeCase.title',\n    description: 'string:randomizeCase.description',\n    shortDescription: 'string:randomizeCase.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/randomize-case/randomize-case.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { randomizeCase } from './service';\n\ndescribe('randomizeCase', () => {\n  it('should randomize the case of each character in the string', () => {\n    const input = 'hello world';\n    const result = randomizeCase(input);\n\n    // Ensure the output length is the same\n    expect(result).toHaveLength(input.length);\n\n    // Ensure each character in the input string appears in the result\n    for (let i = 0; i < input.length; i++) {\n      const inputChar = input[i];\n      const resultChar = result[i];\n\n      if (/[a-zA-Z]/.test(inputChar)) {\n        expect([inputChar.toLowerCase(), inputChar.toUpperCase()]).toContain(\n          resultChar\n        );\n      } else {\n        expect(inputChar).toBe(resultChar);\n      }\n    }\n  });\n\n  it('should handle an empty string', () => {\n    const input = '';\n    const result = randomizeCase(input);\n    expect(result).toBe('');\n  });\n\n  it('should handle a string with numbers and symbols', () => {\n    const input = '123 hello! @world';\n    const result = randomizeCase(input);\n\n    // Ensure the output length is the same\n    expect(result).toHaveLength(input.length);\n\n    // Ensure numbers and symbols remain unchanged\n    for (let i = 0; i < input.length; i++) {\n      const inputChar = input[i];\n      const resultChar = result[i];\n\n      if (!/[a-zA-Z]/.test(inputChar)) {\n        expect(inputChar).toBe(resultChar);\n      }\n    }\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/randomize-case/service.ts",
    "content": "export function randomizeCase(input: string): string {\n  return input\n    .split('')\n    .map((char) =>\n      Math.random() < 0.5 ? char.toLowerCase() : char.toUpperCase()\n    )\n    .join('');\n}\n"
  },
  {
    "path": "src/pages/tools/string/remove-duplicate-lines/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useRef, useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport ToolExamples, {\n  CardExampleType\n} from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { FormikProps } from 'formik';\nimport removeDuplicateLines, {\n  DuplicateRemovalMode,\n  DuplicateRemoverOptions,\n  NewlineOption\n} from './service';\nimport ToolContent from '@components/ToolContent';\n\n// Initial values for our form\nconst initialValues: DuplicateRemoverOptions = {\n  mode: 'all',\n  newlines: 'filter',\n  sortLines: false,\n  trimTextLines: false\n};\n\n// Operation mode options\nconst operationModes = [\n  {\n    title: 'Remove All Duplicate Lines',\n    description:\n      'If this option is selected, then all repeated lines across entire text are removed, starting from the second occurrence.',\n    value: 'all' as DuplicateRemovalMode\n  },\n  {\n    title: 'Remove Consecutive Duplicate Lines',\n    description:\n      'If this option is selected, then only consecutive repeated lines are removed.',\n    value: 'consecutive' as DuplicateRemovalMode\n  },\n  {\n    title: 'Leave Absolutely Unique Text Lines',\n    description:\n      'If this option is selected, then all lines that appear more than once are removed.',\n    value: 'unique' as DuplicateRemovalMode\n  }\n];\n\n// Newlines options\nconst newlineOptions = [\n  {\n    title: 'Preserve All Newlines',\n    description: 'Leave all empty lines in the output.',\n    value: 'preserve' as NewlineOption\n  },\n  {\n    title: 'Filter All Newlines',\n    description: 'Process newlines as regular lines.',\n    value: 'filter' as NewlineOption\n  },\n  {\n    title: 'Delete All Newlines',\n    description: 'Before filtering uniques, remove all newlines.',\n    value: 'delete' as NewlineOption\n  }\n];\n\n// Example cards for demonstration\nconst exampleCards: CardExampleType<typeof initialValues>[] = [\n  {\n    title: 'Remove Duplicate Items from List',\n    description:\n      'Removes duplicate items from a shopping list, keeping only the first occurrence of each item.',\n    sampleText: `Apples\nBananas\nMilk\nEggs\nBread\nMilk\nCheese\nApples\nYogurt`,\n    sampleResult: `Apples\nBananas\nMilk\nEggs\nBread\nCheese\nYogurt`,\n    sampleOptions: {\n      ...initialValues,\n      mode: 'all',\n      newlines: 'filter',\n      sortLines: false,\n      trimTextLines: false\n    }\n  },\n  {\n    title: 'Clean Consecutive Duplicates',\n    description:\n      'Removes consecutive duplicates from log entries, which often happen when a system repeatedly logs the same error.',\n    sampleText: `[INFO] Application started\n[ERROR] Connection failed\n[ERROR] Connection failed\n[ERROR] Connection failed\n[INFO] Retrying connection\n[ERROR] Authentication error\n[ERROR] Authentication error\n[INFO] Connection established`,\n    sampleResult: `[INFO] Application started\n[ERROR] Connection failed\n[INFO] Retrying connection\n[ERROR] Authentication error\n[INFO] Connection established`,\n    sampleOptions: {\n      ...initialValues,\n      mode: 'consecutive',\n      newlines: 'filter',\n      sortLines: false,\n      trimTextLines: false\n    }\n  },\n  {\n    title: 'Extract Unique Entries Only',\n    description:\n      'Filters a list to keep only entries that appear exactly once, removing any duplicated items entirely.',\n    sampleText: `Red\nBlue\nGreen\nBlue\nYellow\nPurple\nRed\nOrange`,\n    sampleResult: `Green\nYellow\nPurple\nOrange`,\n    sampleOptions: {\n      ...initialValues,\n      mode: 'unique',\n      newlines: 'filter',\n      sortLines: false,\n      trimTextLines: false\n    }\n  },\n  {\n    title: 'Sort and Clean Data',\n    description:\n      'Removes duplicate items from a list, trims whitespace, and sorts the results alphabetically.',\n    sampleText: `  Apple\nBanana\n Cherry\nApple\n   Banana\nDragonfruit\n Elderberry `,\n    sampleResult: `Apple\nBanana\nCherry\nDragonfruit\nElderberry`,\n    sampleOptions: {\n      ...initialValues,\n      mode: 'all',\n      newlines: 'filter',\n      sortLines: true,\n      trimTextLines: true\n    }\n  }\n];\n\nexport default function RemoveDuplicateLines({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const computeExternal = (\n    optionsValues: typeof initialValues,\n    inputText: string\n  ) => {\n    setResult(removeDuplicateLines(inputText, optionsValues));\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={\n        <ToolTextResult title={'Text without duplicates'} value={result} />\n      }\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: 'Operation Mode',\n          component: operationModes.map(({ title, description, value }) => (\n            <SimpleRadio\n              key={value}\n              checked={value === values.mode}\n              title={title}\n              description={description}\n              onClick={() => updateField('mode', value)}\n            />\n          ))\n        },\n        {\n          title: 'Newlines, Tabs and Spaces',\n          component: [\n            ...newlineOptions.map(({ title, description, value }) => (\n              <SimpleRadio\n                key={value}\n                checked={value === values.newlines}\n                title={title}\n                description={description}\n                onClick={() => updateField('newlines', value)}\n              />\n            )),\n            <CheckboxWithDesc\n              key=\"trimTextLines\"\n              checked={values.trimTextLines}\n              title=\"Trim Text Lines\"\n              description=\"Before filtering uniques, remove tabs and spaces from the beginning and end of all lines.\"\n              onChange={(checked) => updateField('trimTextLines', checked)}\n            />\n          ]\n        },\n        {\n          title: 'Sort Lines',\n          component: [\n            <CheckboxWithDesc\n              key=\"sortLines\"\n              checked={values.sortLines}\n              title=\"Sort the Output Lines\"\n              description=\"After removing the duplicates, sort the unique lines.\"\n              onChange={(checked) => updateField('sortLines', checked)}\n            />\n          ]\n        }\n      ]}\n      compute={computeExternal}\n      setInput={setInput}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/remove-duplicate-lines/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'remove-duplicate-lines',\n  icon: 'pepicons-print:duplicate-off',\n\n  keywords: ['remove', 'duplicate', 'lines'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:removeDuplicateLines.title',\n    description: 'string:removeDuplicateLines.description',\n    shortDescription: 'string:removeDuplicateLines.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/remove-duplicate-lines/remove-duplicate-lines.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport removeDuplicateLines, { DuplicateRemoverOptions } from './service';\n\ndescribe('removeDuplicateLines function', () => {\n  // Test for 'all' duplicate removal mode\n  describe('mode: all', () => {\n    it('should remove all duplicates keeping first occurrence', () => {\n      const input = 'line1\\nline2\\nline1\\nline3\\nline2';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'filter',\n        sortLines: false,\n        trimTextLines: false\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('line1\\nline2\\nline3');\n    });\n\n    it('should handle case-sensitive duplicates correctly', () => {\n      const input = 'Line1\\nline1\\nLine2\\nline2';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'filter',\n        sortLines: false,\n        trimTextLines: false\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('Line1\\nline1\\nLine2\\nline2');\n    });\n  });\n\n  // Test for 'consecutive' duplicate removal mode\n  describe('mode: consecutive', () => {\n    it('should remove only consecutive duplicates', () => {\n      const input = 'line1\\nline1\\nline2\\nline3\\nline3\\nline1';\n      const options: DuplicateRemoverOptions = {\n        mode: 'consecutive',\n        newlines: 'filter',\n        sortLines: false,\n        trimTextLines: false\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('line1\\nline2\\nline3\\nline1');\n    });\n  });\n\n  // Test for 'unique' duplicate removal mode\n  describe('mode: unique', () => {\n    it('should keep only lines that appear exactly once', () => {\n      const input = 'line1\\nline2\\nline1\\nline3\\nline4\\nline4';\n      const options: DuplicateRemoverOptions = {\n        mode: 'unique',\n        newlines: 'filter',\n        sortLines: false,\n        trimTextLines: false\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('line2\\nline3');\n    });\n  });\n\n  // Test for newlines handling\n  describe('newlines option', () => {\n    it('should filter newlines when newlines is set to filter', () => {\n      const input = 'line1\\n\\nline2\\n\\n\\nline3';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'filter',\n        sortLines: false,\n        trimTextLines: false\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('line1\\n\\nline2\\nline3');\n    });\n\n    it('should delete newlines when newlines is set to delete', () => {\n      const input = 'line1\\n\\nline2\\n\\n\\nline3';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'delete',\n        sortLines: false,\n        trimTextLines: false\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('line1\\nline2\\nline3');\n    });\n\n    it('should preserve newlines when newlines is set to preserve', () => {\n      const input = 'line1\\n\\nline2\\n\\nline2\\nline3';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'preserve',\n        sortLines: false,\n        trimTextLines: false\n      };\n      const result = removeDuplicateLines(input, options);\n      // This test needs careful consideration of the expected behavior\n      expect(result).not.toContain('line2\\nline2');\n      expect(result).toContain('line1');\n      expect(result).toContain('line2');\n      expect(result).toContain('line3');\n    });\n  });\n\n  // Test for sorting\n  describe('sortLines option', () => {\n    it('should sort lines when sortLines is true', () => {\n      const input = 'line3\\nline1\\nline2';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'filter',\n        sortLines: true,\n        trimTextLines: false\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('line1\\nline2\\nline3');\n    });\n  });\n\n  // Test for trimming\n  describe('trimTextLines option', () => {\n    it('should trim lines when trimTextLines is true', () => {\n      const input = '  line1  \\n  line2  \\nline3';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'filter',\n        sortLines: false,\n        trimTextLines: true\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('line1\\nline2\\nline3');\n    });\n\n    it('should consider trimmed lines as duplicates', () => {\n      const input = '  line1  \\nline1\\n  line2\\nline2  ';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'filter',\n        sortLines: false,\n        trimTextLines: true\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('line1\\nline2');\n    });\n  });\n\n  // Combined scenarios\n  describe('combined options', () => {\n    it('should handle all options together correctly', () => {\n      const input = '  line3  \\nline1\\n\\nline3\\nline2\\nline1';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'delete',\n        sortLines: true,\n        trimTextLines: true\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('line1\\nline2\\nline3');\n    });\n  });\n\n  // Edge cases\n  describe('edge cases', () => {\n    it('should handle empty input', () => {\n      const input = '';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'filter',\n        sortLines: false,\n        trimTextLines: false\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('');\n    });\n\n    it('should handle input with only newlines', () => {\n      const input = '\\n\\n\\n';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'filter',\n        sortLines: false,\n        trimTextLines: false\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('');\n    });\n\n    it('should handle input with only whitespace', () => {\n      const input = '   \\n  \\n    ';\n      const options: DuplicateRemoverOptions = {\n        mode: 'all',\n        newlines: 'filter',\n        sortLines: false,\n        trimTextLines: true\n      };\n      const result = removeDuplicateLines(input, options);\n      expect(result).toBe('');\n    });\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/remove-duplicate-lines/service.ts",
    "content": "export type NewlineOption = 'preserve' | 'filter' | 'delete';\nexport type DuplicateRemovalMode = 'all' | 'consecutive' | 'unique';\n\nexport interface DuplicateRemoverOptions {\n  mode: DuplicateRemovalMode;\n  newlines: NewlineOption;\n  sortLines: boolean;\n  trimTextLines: boolean;\n}\n\n/**\n * Removes duplicate lines from text based on specified options\n * @param text The input text to process\n * @param options Configuration options for text processing\n * @returns Processed text with duplicates removed according to options\n */\nexport default function removeDuplicateLines(\n  text: string,\n  options: DuplicateRemoverOptions\n): string {\n  // Split the text into individual lines\n  let lines = text.split('\\n');\n\n  // Process newlines based on option\n  if (options.newlines === 'delete') {\n    // Remove all empty lines\n    lines = lines.filter((line) => line.trim() !== '');\n  }\n\n  // Trim lines if option is selected\n  if (options.trimTextLines) {\n    lines = lines.map((line) => line.trim());\n  }\n\n  // Remove duplicates based on mode\n  let processedLines: string[] = [];\n\n  if (options.mode === 'all') {\n    // Remove all duplicates, keeping only first occurrence\n    const seen = new Set<string>();\n    processedLines = lines.filter((line) => {\n      if (seen.has(line)) {\n        return false;\n      }\n      seen.add(line);\n      return true;\n    });\n  } else if (options.mode === 'consecutive') {\n    // Remove only consecutive duplicates\n    processedLines = lines.filter((line, index, arr) => {\n      return index === 0 || line !== arr[index - 1];\n    });\n  } else if (options.mode === 'unique') {\n    // Leave only absolutely unique lines\n    const lineCount = new Map<string, number>();\n    lines.forEach((line) => {\n      lineCount.set(line, (lineCount.get(line) || 0) + 1);\n    });\n\n    processedLines = lines.filter((line) => lineCount.get(line) === 1);\n  }\n\n  // Sort lines if option is selected\n  if (options.sortLines) {\n    processedLines.sort();\n  }\n\n  // Process newlines for output\n  if (options.newlines === 'filter') {\n    // Process newlines as regular lines (already done by default)\n  } else if (options.newlines === 'preserve') {\n    // Make sure empty lines are preserved in the output\n    processedLines = text.split('\\n').map((line) => {\n      if (line.trim() === '') return line;\n      return processedLines.includes(line) ? line : '';\n    });\n  }\n\n  return processedLines.join('\\n');\n}\n\n// Example usage:\n// const result = removeDuplicateLines(inputText, {\n//   mode: 'all',\n//   newlines: 'filter',\n//   sortLines: false,\n//   trimTextLines: true\n// });\n"
  },
  {
    "path": "src/pages/tools/string/repeat/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport { useState } from 'react';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { repeatText } from './service';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport { initialValues, InitialValuesType } from './initialValues';\nimport ToolContent from '@components/ToolContent';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Repeat word five times',\n    description: 'Repeats \"Hello!\" five times without any delimiter.',\n    sampleText: 'Hello! ',\n    sampleResult: 'Hello! Hello! Hello! Hello! Hello! ',\n    sampleOptions: {\n      textToRepeat: 'Hello! ',\n      repeatAmount: '5',\n      delimiter: ''\n    }\n  },\n  {\n    title: 'Repeat phrase with comma',\n    description:\n      'Repeats \"Good job\" three times, separated by commas and spaces.',\n    sampleText: 'Good job',\n    sampleResult: 'Good job, Good job, Good job',\n    sampleOptions: {\n      textToRepeat: 'Good job',\n      repeatAmount: '3',\n      delimiter: ', '\n    }\n  },\n  {\n    title: 'Repeat number with space',\n    description: 'Repeats the number \"42\" four times, separated by spaces.',\n    sampleText: '42',\n    sampleResult: '42 42 42 42',\n    sampleOptions: {\n      textToRepeat: '42',\n      repeatAmount: '4',\n      delimiter: ' '\n    }\n  }\n];\n\nexport default function Replacer({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  function compute(optionsValues: InitialValuesType, input: string) {\n    setResult(repeatText(optionsValues, input));\n  }\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('repeat.textRepetitions'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            description={t('repeat.repeatAmountDescription')}\n            placeholder={t('repeat.numberPlaceholder')}\n            value={values.repeatAmount}\n            onOwnChange={(val) => updateField('repeatAmount', val)}\n            type={'number'}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('repeat.repetitionsDelimiter'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            description={t('repeat.delimiterDescription')}\n            placeholder={t('repeat.delimiterPlaceholder')}\n            value={values.delimiter}\n            onOwnChange={(val) => updateField('delimiter', val)}\n            type={'text'}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('repeat.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('repeat.resultTitle')} value={result} />\n      }\n      toolInfo={{\n        title: t('repeat.toolInfo.title'),\n        description: t('repeat.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/repeat/initialValues.ts",
    "content": "export type InitialValuesType = {\n  textToRepeat: string;\n  repeatAmount: string;\n  delimiter: string;\n};\n\nexport const initialValues: InitialValuesType = {\n  textToRepeat: '',\n  repeatAmount: '5',\n  delimiter: ''\n};\n"
  },
  {
    "path": "src/pages/tools/string/repeat/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'repeat',\n\n  icon: 'material-symbols-light:replay',\n\n  keywords: ['text', 'repeat'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:repeat.title',\n    description: 'string:repeat.description',\n    shortDescription: 'string:repeat.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/repeat/repeatText.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { repeatText } from './service';\nimport { initialValues } from './initialValues';\n\ndescribe('repeatText function', () => {\n  it('should repeat the letter correctly', () => {\n    const text = 'i';\n    const repeatAmount = '5';\n    const result = repeatText({ ...initialValues, repeatAmount }, text);\n    expect(result).toBe('iiiii');\n  });\n\n  it('should repeat the word correctly', () => {\n    const text = 'hello';\n    const repeatAmount = '3';\n    const result = repeatText({ ...initialValues, repeatAmount }, text);\n    expect(result).toBe('hellohellohello');\n  });\n\n  it('should repeat the word with a space delimiter correctly', () => {\n    const text = 'word';\n    const repeatAmount = '3';\n    const delimiter = ' ';\n    const result = repeatText(\n      { ...initialValues, repeatAmount, delimiter },\n      text\n    );\n    expect(result).toBe('word word word');\n  });\n\n  it('should repeat the word with a space and a comma delimiter correctly', () => {\n    const text = 'test';\n    const repeatAmount = '3';\n    const delimiter = ', ';\n    const result = repeatText(\n      { ...initialValues, repeatAmount, delimiter },\n      text\n    );\n    expect(result).toBe('test, test, test');\n  });\n\n  it('Should not repeat text if repeatAmount is zero', () => {\n    const text = 'something';\n    const repeatAmount = '0';\n    const result = repeatText({ ...initialValues, repeatAmount }, text);\n    expect(result).toBe('');\n  });\n\n  it('Should not repeat text if repeatAmount is not entered', () => {\n    const text = 'something';\n    const repeatAmount = '';\n    const result = repeatText({ ...initialValues, repeatAmount }, text);\n    expect(result).toBe('');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/repeat/service.ts",
    "content": "import { InitialValuesType } from './initialValues';\n\nexport function repeatText(options: InitialValuesType, text: string) {\n  const { repeatAmount, delimiter } = options;\n\n  const parsedAmount = parseInt(repeatAmount) || 0;\n\n  return Array(parsedAmount).fill(text).join(delimiter);\n}\n"
  },
  {
    "path": "src/pages/tools/string/reverse/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState, useRef } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport ToolOptions, { GetGroupsType } from '@components/options/ToolOptions';\nimport { stringReverser } from './service';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport ToolInputAndResult from '@components/ToolInputAndResult';\nimport ToolExamples, {\n  CardExampleType\n} from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { FormikProps } from 'formik';\nimport ToolContent from '@components/ToolContent';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  multiLine: true,\n  emptyItems: false,\n  trim: false\n};\n\nconst exampleCards: CardExampleType<typeof initialValues>[] = [\n  {\n    title: 'Simple Text Reversal',\n    description:\n      'Reverses each character in the text. Perfect for creating mirror text.',\n    sampleText: 'Hello World',\n    sampleResult: 'dlroW olleH',\n    sampleOptions: {\n      ...initialValues,\n      multiLine: false\n    }\n  },\n  {\n    title: 'Multi-line Reversal',\n    description:\n      'Reverses each line independently while preserving the line breaks.',\n    sampleText: 'First line\\nSecond line\\nThird line',\n    sampleResult: 'enil tsriF\\nenil dnoceS\\nenil drihT',\n    sampleOptions: {\n      ...initialValues,\n      multiLine: true\n    }\n  },\n  {\n    title: 'Clean Reversed Text',\n    description:\n      'Trims whitespace and skips empty lines before reversing the text.',\n    sampleText: '  Spaces removed  \\n\\nEmpty line skipped',\n    sampleResult: 'devomer secapS\\ndeppiks enil ytpmE',\n    sampleOptions: {\n      ...initialValues,\n      multiLine: true,\n      emptyItems: true,\n      trim: true\n    }\n  }\n];\n\nexport default function Reverse({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const computeExternal = (\n    optionsValues: typeof initialValues,\n    input: string\n  ) => {\n    const { multiLine, emptyItems, trim } = optionsValues;\n    setResult(stringReverser(input, multiLine, emptyItems, trim));\n  };\n\n  const getGroups: GetGroupsType<typeof initialValues> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('reverse.reversalOptions'),\n      component: [\n        <CheckboxWithDesc\n          key=\"multiLine\"\n          checked={values.multiLine}\n          title={t('reverse.processMultiLine')}\n          description={t('reverse.processMultiLineDescription')}\n          onChange={(val) => updateField('multiLine', val)}\n        />,\n        <CheckboxWithDesc\n          key=\"emptyItems\"\n          checked={values.emptyItems}\n          title={t('reverse.skipEmptyLines')}\n          description={t('reverse.skipEmptyLinesDescription')}\n          onChange={(val) => updateField('emptyItems', val)}\n        />,\n        <CheckboxWithDesc\n          key=\"trim\"\n          checked={values.trim}\n          title={t('reverse.trimWhitespace')}\n          description={t('reverse.trimWhitespaceDescription')}\n          onChange={(val) => updateField('trim', val)}\n        />\n      ]\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={computeExternal}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('reverse.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('reverse.resultTitle')} value={result} />\n      }\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/reverse/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'reverse',\n  icon: 'material-symbols-light:swap-horiz',\n\n  keywords: ['reverse'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:reverse.title',\n    description: 'string:reverse.description',\n    shortDescription: 'string:reverse.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/reverse/reverse.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { stringReverser } from './service';\n\ndescribe('stringReverser', () => {\n  it('should reverse a single-line string', () => {\n    const input = 'hello world';\n    const result = stringReverser(input, false, false, false);\n    expect(result).toBe('dlrow olleh');\n  });\n\n  it('should reverse each line in a multi-line string', () => {\n    const input = 'hello\\nworld';\n    const result = stringReverser(input, true, false, false);\n    expect(result).toBe('olleh\\ndlrow');\n  });\n\n  it('should remove empty items if emptyItems is true', () => {\n    const input = 'hello\\n\\nworld';\n    const result = stringReverser(input, true, true, false);\n    expect(result).toBe('olleh\\ndlrow');\n  });\n\n  it('should trim each line if trim is true', () => {\n    const input = '  hello  \\n  world  ';\n    const result = stringReverser(input, true, false, true);\n    expect(result).toBe('olleh\\ndlrow');\n  });\n\n  it('should handle empty input', () => {\n    const input = '';\n    const result = stringReverser(input, false, false, false);\n    expect(result).toBe('');\n  });\n\n  it('should handle a single line with emptyItems and trim', () => {\n    const input = '  hello world  ';\n    const result = stringReverser(input, false, true, true);\n    expect(result).toBe('dlrow olleh');\n  });\n\n  it('should handle a single line with emptyItems and non trim', () => {\n    const input = '  hello world  ';\n    const result = stringReverser(input, false, true, false);\n    expect(result).toBe('  dlrow olleh  ');\n  });\n\n  it('should handle a multi line with emptyItems and non trim', () => {\n    const input = '  hello\\n\\n\\n\\nworld  ';\n    const result = stringReverser(input, true, true, false);\n    expect(result).toBe('olleh  \\n  dlrow');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/reverse/service.ts",
    "content": "import { reverseString } from 'utils/string';\n\nexport function stringReverser(\n  input: string,\n  multiLine: boolean,\n  emptyItems: boolean,\n  trim: boolean\n) {\n  let array: string[] = [];\n  let result: string[] = [];\n\n  // split the input in multiLine mode\n  if (multiLine) {\n    array = input.split('\\n');\n  } else {\n    array.push(input);\n  }\n\n  // handle empty items\n  if (emptyItems) {\n    array = array.filter(Boolean);\n  }\n  // Handle trim\n  if (trim) {\n    array = array.map((line) => line.trim());\n  }\n\n  result = array.map((element) => reverseString(element));\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/string/rot13/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { rot13 } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\n\ntype InitialValuesType = Record<string, never>;\n\nconst initialValues: InitialValuesType = {};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Encode a message with ROT13',\n    description:\n      'This example shows how to encode a simple message using ROT13 cipher.',\n    sampleText: 'Hello, World!',\n    sampleResult: 'Uryyb, Jbeyq!',\n    sampleOptions: {}\n  },\n  {\n    title: 'Decode a ROT13 message',\n    description:\n      'This example shows how to decode a message that was encoded with ROT13.',\n    sampleText: 'Uryyb, Jbeyq!',\n    sampleResult: 'Hello, World!',\n    sampleOptions: {}\n  }\n];\n\nexport default function Rot13({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (_: InitialValuesType, input: string) => {\n    if (input) setResult(rot13(input));\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolTextInput\n          title={t('rot13.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('rot13.resultTitle')} value={result} />\n      }\n      initialValues={initialValues}\n      getGroups={null}\n      toolInfo={{\n        title: t('rot13.toolInfo.title'),\n        description: t('rot13.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n      input={input}\n      setInput={setInput}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/rot13/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('string', {\n  i18n: {\n    name: 'string:rot13.title',\n    description: 'string:rot13.description',\n    shortDescription: 'string:rot13.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  },\n\n  path: 'rot13',\n  icon: 'hugeicons:encrypt',\n\n  keywords: ['rot13'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/string/rot13/rot13.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { rot13 } from './service';\n\ndescribe('rot13', () => {\n  it('should encode a simple string using ROT13', () => {\n    const input = 'hello';\n    const result = rot13(input);\n    expect(result).toBe('uryyb');\n  });\n\n  it('should decode a ROT13 encoded string', () => {\n    const input = 'uryyb';\n    const result = rot13(input);\n    expect(result).toBe('hello');\n  });\n\n  it('should handle uppercase letters correctly', () => {\n    const input = 'HELLO';\n    const result = rot13(input);\n    expect(result).toBe('URYYB');\n  });\n\n  it('should handle mixed case letters correctly', () => {\n    const input = 'HelloWorld';\n    const result = rot13(input);\n    expect(result).toBe('UryybJbeyq');\n  });\n\n  it('should handle non-alphabetic characters correctly', () => {\n    const input = 'Hello, World!';\n    const result = rot13(input);\n    expect(result).toBe('Uryyb, Jbeyq!');\n  });\n\n  it('should handle an empty string', () => {\n    const input = '';\n    const result = rot13(input);\n    expect(result).toBe('');\n  });\n\n  it('should handle a string with numbers correctly', () => {\n    const input = '1234';\n    const result = rot13(input);\n    expect(result).toBe('1234');\n  });\n\n  it('should handle a string with symbols correctly', () => {\n    const input = '!@#$%^&*()_+-=';\n    const result = rot13(input);\n    expect(result).toBe('!@#$%^&*()_+-=');\n  });\n\n  it('should handle a string with mixed characters correctly', () => {\n    const input = 'Hello, World! 123';\n    const result = rot13(input);\n    expect(result).toBe('Uryyb, Jbeyq! 123');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/rot13/service.ts",
    "content": "export function rot13(input: string): string {\n  return input.replace(/[a-zA-Z]/g, (char) => {\n    const charCode = char.charCodeAt(0);\n    const baseCode = charCode >= 97 ? 97 : 65; // 'a' or 'A'\n    return String.fromCharCode(((charCode - baseCode + 13) % 26) + baseCode);\n  });\n}\n"
  },
  {
    "path": "src/pages/tools/string/rotate/index.tsx",
    "content": "import React, { useState } from 'react';\nimport { Box } from '@mui/material';\nimport ToolContent from '@components/ToolContent';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { rotateString } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { useTranslation } from 'react-i18next';\n\ninterface InitialValuesType {\n  step: string;\n  direction: 'left' | 'right';\n  multiLine: boolean;\n}\n\nconst initialValues: InitialValuesType = {\n  step: '1',\n  direction: 'right',\n  multiLine: true\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Rotate text to the right',\n    description:\n      'This example shows how to rotate text to the right by 2 positions.',\n    sampleText: 'abcdef',\n    sampleResult: 'efabcd',\n    sampleOptions: {\n      step: '2',\n      direction: 'right',\n      multiLine: false\n    }\n  },\n  {\n    title: 'Rotate text to the left',\n    description:\n      'This example shows how to rotate text to the left by 2 positions.',\n    sampleText: 'abcdef',\n    sampleResult: 'cdefab',\n    sampleOptions: {\n      step: '2',\n      direction: 'left',\n      multiLine: false\n    }\n  },\n  {\n    title: 'Rotate multi-line text',\n    description:\n      'This example shows how to rotate each line of a multi-line text.',\n    sampleText: 'abcdef\\nghijkl',\n    sampleResult: 'fabcde\\nlghijk',\n    sampleOptions: {\n      step: '1',\n      direction: 'right',\n      multiLine: true\n    }\n  }\n];\n\nexport default function Rotate({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: string) => {\n    if (input) {\n      const step = parseInt(optionsValues.step, 10) || 1;\n      const isRight = optionsValues.direction === 'right';\n      setResult(rotateString(input, step, isRight, optionsValues.multiLine));\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('rotate.rotationOptions'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.step}\n            onOwnChange={(val) => updateField('step', val)}\n            description={t('rotate.stepDescription')}\n            type=\"number\"\n          />\n          <SimpleRadio\n            onClick={() => updateField('direction', 'right')}\n            checked={values.direction === 'right'}\n            title={t('rotate.rotateRight')}\n          />\n          <SimpleRadio\n            onClick={() => updateField('direction', 'left')}\n            checked={values.direction === 'left'}\n            title={t('rotate.rotateLeft')}\n          />\n          <CheckboxWithDesc\n            checked={values.multiLine}\n            onChange={(checked) => updateField('multiLine', checked)}\n            title={t('rotate.processAsMultiLine')}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      inputComponent={\n        <ToolTextInput\n          title={t('rotate.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('rotate.resultTitle')} value={result} />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      toolInfo={{\n        title: t('rotate.toolInfo.title'),\n        description: t('rotate.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n      input={input}\n      setInput={setInput}\n      compute={compute}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/rotate/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('string', {\n  i18n: {\n    name: 'string:rotate.title',\n    description: 'string:rotate.description',\n    shortDescription: 'string:rotate.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  },\n\n  path: 'rotate',\n  icon: 'carbon:rotate',\n\n  keywords: ['rotate'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/string/rotate/rotate.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\n\nimport { rotate, rotateString } from './service'; // Adjust the import path as necessary\n\ndescribe('rotate function', () => {\n  it('rotates right correctly', () => {\n    expect(rotate('abcdef', 2, true)).toBe('efabcd');\n    expect(rotate('abcdef', 1, true)).toBe('fabcde');\n    expect(rotate('abcdef', 6, true)).toBe('abcdef'); // full rotation\n    expect(rotate('abcdef', 7, true)).toBe('fabcde'); // beyond full rotation\n  });\n\n  it('rotates left correctly', () => {\n    expect(rotate('abcdef', 2, false)).toBe('cdefab');\n    expect(rotate('abcdef', 1, false)).toBe('bcdefa');\n    expect(rotate('abcdef', 6, false)).toBe('abcdef'); // full rotation\n    expect(rotate('abcdef', 7, false)).toBe('bcdefa'); // beyond full rotation\n  });\n\n  it('handles empty string', () => {\n    expect(rotate('', 2, true)).toBe('');\n    expect(rotate('', 2, false)).toBe('');\n  });\n\n  it('handles single character string', () => {\n    expect(rotate('a', 2, true)).toBe('a');\n    expect(rotate('a', 2, false)).toBe('a');\n  });\n});\n\ndescribe('rotateString function', () => {\n  it('rotates single-line string right', () => {\n    expect(rotateString('abcdef', 2, true, false)).toBe('efabcd');\n  });\n\n  it('rotates single-line string left', () => {\n    expect(rotateString('abcdef', 2, false, false)).toBe('cdefab');\n  });\n\n  it('rotates multi-line string right', () => {\n    const input = 'abcdef\\nghijkl';\n    const expected = 'efabcd\\nklghij';\n    expect(rotateString(input, 2, true, true)).toBe(expected);\n  });\n\n  it('rotates multi-line string left', () => {\n    const input = 'abcdef\\nghijkl';\n    const expected = 'cdefab\\nijklgh';\n    expect(rotateString(input, 2, false, true)).toBe(expected);\n  });\n\n  it('handles empty string in multi-line mode', () => {\n    expect(rotateString('', 2, true, true)).toBe('');\n  });\n\n  it('handles single character string in multi-line mode', () => {\n    expect(rotateString('a', 2, true, true)).toBe('a');\n  });\n\n  it('handles single character string in single-line mode', () => {\n    expect(rotateString('a', 2, true, false)).toBe('a');\n  });\n\n  it('handles string with multiple empty lines', () => {\n    const input = 'abcdef\\n\\nxyz';\n    const expected = 'efabcd\\n\\nyzx';\n    expect(rotateString(input, 2, true, true)).toBe(expected);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/rotate/service.ts",
    "content": "export function rotate(input: string, step: number, right: boolean): string {\n  const array: string[] = input.split('');\n  const length = array.length;\n\n  if (length === 0) {\n    return input; // Return early if the input is an empty string\n  }\n\n  // Normalize the step to be within the bounds of the array length\n  const normalizedPositions = ((step % length) + length) % length;\n\n  if (right) {\n    // Rotate right\n    return array\n      .slice(-normalizedPositions)\n      .concat(array.slice(0, -normalizedPositions))\n      .join('');\n  } else {\n    // Rotate left\n    return array\n      .slice(normalizedPositions)\n      .concat(array.slice(0, normalizedPositions))\n      .join('');\n  }\n}\n\nexport function rotateString(\n  input: string,\n  step: number,\n  right: boolean,\n  multiLine: boolean\n) {\n  const result: string[] = [];\n  if (multiLine) {\n    const array: string[] = input.split('\\n');\n    for (const word of array) {\n      result.push(rotate(word, step, right));\n    }\n  } else {\n    result.push(rotate(input, step, right));\n  }\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/string/split/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useRef, useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { compute, SplitOperatorType } from './service';\nimport RadioWithTextField from '@components/options/RadioWithTextField';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport ToolExamples, {\n  CardExampleType\n} from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { FormikProps } from 'formik';\nimport ToolContent from '@components/ToolContent';\nimport { useTranslation } from 'react-i18next';\nimport { ParseKeys } from 'i18next';\n\nconst initialValues = {\n  splitSeparatorType: 'symbol' as SplitOperatorType,\n  symbolValue: ' ',\n  regexValue: '/\\\\s+/',\n  lengthValue: '16',\n  chunksValue: '4',\n\n  outputSeparator: '\\\\n',\n  charBeforeChunk: '',\n  charAfterChunk: ''\n};\nconst splitOperators: {\n  title: ParseKeys<'string'>;\n  description: ParseKeys<'string'>;\n  type: SplitOperatorType;\n}[] = [\n  {\n    title: 'split.symbolTitle',\n    description: 'split.symbolDescription',\n    type: 'symbol'\n  },\n  {\n    title: 'split.regexTitle',\n    type: 'regex',\n    description: 'split.regexDescription'\n  },\n  {\n    title: 'split.lengthTitle',\n    description: 'split.lengthDescription',\n    type: 'length'\n  },\n  {\n    title: 'split.chunksTitle',\n    description: 'split.chunksDescription',\n    type: 'chunks'\n  }\n];\nconst outputOptions: {\n  description: ParseKeys<'string'>;\n  accessor: keyof typeof initialValues;\n}[] = [\n  {\n    description: 'split.outputSeparatorDescription',\n    accessor: 'outputSeparator'\n  },\n  {\n    description: 'split.charBeforeChunkDescription',\n    accessor: 'charBeforeChunk'\n  },\n  {\n    description: 'split.charAfterChunkDescription',\n    accessor: 'charAfterChunk'\n  }\n];\n\nconst exampleCards: CardExampleType<typeof initialValues>[] = [\n  {\n    title: 'Split German Numbers',\n    description:\n      'In this example, we break the text into pieces by two characters – a comma and space. As a result, we get a column of numbers from 1 to 10 in German.',\n    sampleText: `1 - eins, 2 - zwei, 3 - drei, 4 - vier, 5 - fünf, 6 - sechs, 7 - sieben, 8 - acht, 9 - neun, 10 - zehn`,\n    sampleResult: `1 - eins\n2 - zwei\n3 - drei\n4 - vier\n5 - fünf\n6 - sechs\n7 - sieben\n8 - acht\n9 - neun\n10 - zehn`,\n    sampleOptions: {\n      ...initialValues,\n      symbolValue: ',',\n      splitSeparatorType: 'symbol',\n      outputSeparator: '\\n'\n    }\n  },\n  {\n    title: 'Text Cleanup via a Regular Expression',\n    description:\n      'In this example, we use a super smart regular expression trick to clean-up the text. This regexp finds all non-alphabetic characters and splits the text into pieces by these non-alphabetic chars. As a result, we extract only those parts of the text that contain Latin letters and words.',\n    sampleText: `Finding%№1.65*;?words()is'12#easy_`,\n    sampleResult: `Finding\nwords\nis\neasy`,\n    sampleOptions: {\n      ...initialValues,\n      regexValue: '[^a-zA-Z]+',\n      splitSeparatorType: 'regex',\n      outputSeparator: '\\n'\n    }\n  },\n  {\n    title: 'Three-dot Output Separator',\n    description:\n      'This example splits the text by spaces and then places three dots between the words.',\n    sampleText: `If you started with $0.01 and doubled your money every day, it would take 27 days to become a millionaire.`,\n    sampleResult: `If...you...started...with...$0.01...and...doubled...your...money...every...day,...it...would...take...27...days...to...become...a...millionaire.!`,\n    sampleOptions: {\n      ...initialValues,\n      symbolValue: '',\n      splitSeparatorType: 'symbol',\n      outputSeparator: '...'\n    }\n  }\n];\n\nexport default function SplitText({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const computeExternal = (\n    optionsValues: typeof initialValues,\n    input: string\n  ) => {\n    const {\n      splitSeparatorType,\n      outputSeparator,\n      charBeforeChunk,\n      charAfterChunk,\n      chunksValue,\n      symbolValue,\n      regexValue,\n      lengthValue\n    } = optionsValues;\n\n    setResult(\n      compute(\n        splitSeparatorType,\n        input,\n        symbolValue,\n        regexValue,\n        Number(lengthValue),\n        Number(chunksValue),\n        charBeforeChunk,\n        charAfterChunk,\n        outputSeparator\n      )\n    );\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={\n        <ToolTextResult title={t('split.resultTitle')} value={result} />\n      }\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('split.splitSeparatorOptions'),\n          component: splitOperators.map(({ title, description, type }) => (\n            <RadioWithTextField\n              key={type}\n              checked={type === values.splitSeparatorType}\n              title={t(title)}\n              fieldName={'splitSeparatorType'}\n              description={t(description)}\n              value={values[`${type}Value`]}\n              onRadioClick={() => updateField('splitSeparatorType', type)}\n              onTextChange={(val) => updateField(`${type}Value`, val)}\n            />\n          ))\n        },\n        {\n          title: t('split.outputSeparatorOptions'),\n          component: outputOptions.map((option) => (\n            <TextFieldWithDesc\n              key={option.accessor}\n              value={values[option.accessor]}\n              onOwnChange={(value) => updateField(option.accessor, value)}\n              description={t(option.description)}\n            />\n          ))\n        }\n      ]}\n      compute={computeExternal}\n      setInput={setInput}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/split/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'split',\n\n  icon: 'material-symbols-light:call-split',\n\n  keywords: ['split'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:split.title',\n    description: 'string:split.description',\n    shortDescription: 'string:split.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/split/service.ts",
    "content": "export type SplitOperatorType = 'symbol' | 'regex' | 'length' | 'chunks';\n\nfunction splitTextByLength(text: string, length: number) {\n  if (length <= 0) throw new Error('Length must be a positive number');\n  const result: string[] = [];\n  for (let i = 0; i < text.length; i += length) {\n    result.push(text.slice(i, i + length));\n  }\n  return result;\n}\n\nfunction splitIntoChunks(text: string, numChunks: number) {\n  if (numChunks <= 0)\n    throw new Error('Number of chunks must be a positive number');\n  const totalLength = text.length;\n  if (totalLength < numChunks)\n    throw new Error(\n      'Text length must be at least as long as the number of chunks'\n    );\n\n  const chunkSize = Math.ceil(totalLength / numChunks); // Calculate the chunk size, rounding up to handle remainders\n  let result = [];\n\n  for (let i = 0; i < totalLength; i += chunkSize) {\n    result.push(text.slice(i, i + chunkSize));\n  }\n\n  // Ensure the result contains exactly numChunks, adjusting the last chunk if necessary\n  if (result.length > numChunks) {\n    result[numChunks - 1] = result.slice(numChunks - 1).join(''); // Merge any extra chunks into the last chunk\n    result = result.slice(0, numChunks); // Take only the first numChunks chunks\n  }\n\n  return result;\n}\n\nexport function compute(\n  splitSeparatorType: SplitOperatorType,\n  input: string,\n  symbolValue: string,\n  regexValue: string,\n  lengthValue: number,\n  chunksValue: number,\n  charBeforeChunk: string,\n  charAfterChunk: string,\n  outputSeparator: string\n) {\n  let splitText;\n  switch (splitSeparatorType) {\n    case 'symbol':\n      splitText = input.split(symbolValue);\n      break;\n    case 'regex':\n      splitText = input.split(new RegExp(regexValue));\n      break;\n    case 'length':\n      splitText = splitTextByLength(input, lengthValue);\n      break;\n    case 'chunks':\n      splitText = splitIntoChunks(input, chunksValue).map(\n        (chunk) => `${charBeforeChunk}${chunk}${charAfterChunk}`\n      );\n  }\n  return splitText.join(outputSeparator);\n}\n"
  },
  {
    "path": "src/pages/tools/string/split/string-split.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { compute } from './service';\n\ndescribe('compute function', () => {\n  it('should split by symbol', () => {\n    const result = compute('symbol', 'hello world', ' ', '', 0, 0, '', '', ',');\n    expect(result).toBe('hello,world');\n  });\n\n  it('should split by regex', () => {\n    const result = compute(\n      'regex',\n      'hello1world2again',\n      '',\n      '\\\\d',\n      0,\n      0,\n      '',\n      '',\n      ','\n    );\n    expect(result).toBe('hello,world,again');\n  });\n\n  it('should split by length', () => {\n    const result = compute('length', 'helloworld', '', '', 3, 0, '', '', ',');\n    expect(result).toBe('hel,low,orl,d');\n  });\n\n  it('should split into chunks', () => {\n    const result = compute(\n      'chunks',\n      'helloworldagain',\n      '',\n      '',\n      0,\n      3,\n      '[',\n      ']',\n      ','\n    );\n    expect(result).toBe('[hello],[world],[again]');\n  });\n\n  it('should handle empty input', () => {\n    const result = compute('symbol', '', ' ', '', 0, 0, '', '', ',');\n    expect(result).toBe('');\n  });\n\n  it('should handle length greater than text length', () => {\n    const result = compute('length', 'hi', '', '', 5, 0, '', '', ',');\n    expect(result).toBe('hi');\n  });\n\n  it('should handle chunks greater than text length', () => {\n    expect(() => {\n      compute('chunks', 'hi', '', '', 0, 5, '', '', ',');\n    }).toThrow('Text length must be at least as long as the number of chunks');\n  });\n\n  it('should handle invalid length', () => {\n    expect(() => {\n      compute('length', 'hello', '', '', -1, 0, '', '', ',');\n    }).toThrow('Length must be a positive number');\n  });\n\n  it('should handle invalid chunks', () => {\n    expect(() => {\n      compute('chunks', 'hello', '', '', 0, 0, '', '', ',');\n    }).toThrow('Number of chunks must be a positive number');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/statistic/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport { useState } from 'react';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { textStatistics } from './service';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport { InitialValuesType } from './types';\nimport ToolContent from '@components/ToolContent';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  emptyLines: false,\n  sentenceDelimiters: '',\n  wordDelimiters: '',\n  characterCount: false,\n  wordCount: false\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Text Statistics without any Flag',\n    description:\n      'This example shows basic text statistics without any additional flags.',\n    sampleText:\n      'Giraffes have long necks that can be up to 6 feet (1.8 meters) long, but they only have 7 neck vertebrae, the same as humans.',\n    sampleResult: `Text Statistics\n==================\nCharacters: 125\nWords: 26\nLines: 1\nSentences: 1\nParagraphs: 1`,\n    sampleOptions: initialValues\n  },\n  {\n    title: 'Text Statistics with Characters Frequency',\n    description:\n      'This example shows basic text statistics with characters frequency.',\n    sampleText: `The Great Barrier Reef is the world's largest coral reef system, located off the coast of Australia. It consists of over 2,900 individual reefs and 900 islands. The reef is home to thousands of species of marine life, including fish, sea turtles, sharks, and dolphins. It is also a popular tourist destination, attracting millions of visitors every year. However, the reef is facing many threats, including climate change, pollution, and overfishing. Conservation efforts are being made to protect this unique and valuable ecosystem for future generations.`,\n    sampleResult: `Text Statistics\n==================\nCharacters: 556\nWords: 87\nLines: 1\nSentences: 1\nParagraphs: 1\n\nCharacters Frequency\n==================\n0: 4 (0.72%)\n2: 1 (0.18%)\n9: 2 (0.36%)\n␣: 85 (15.29%)\ne: 51 (9.17%)\ni: 40 (7.19%)\ns: 40 (7.19%)\nt: 39 (7.01%)\na: 37 (6.65%)\no: 34 (6.12%)\nr: 33 (5.94%)\nn: 29 (5.22%)\nl: 21 (3.78%)\nf: 20 (3.60%)\nh: 15 (2.70%)\nd: 15 (2.70%)\nc: 14 (2.52%)\nu: 14 (2.52%)\n,: 11 (1.98%)\ng: 10 (1.80%)\nm: 8 (1.44%)\nv: 8 (1.44%)\n.: 6 (1.08%)\np: 6 (1.08%)\ny: 5 (0.90%)\nb: 3 (0.54%)\nw: 2 (0.36%)\n': 1 (0.18%)\nk: 1 (0.18%)\nq: 1 (0.18%)`,\n    sampleOptions: {\n      emptyLines: false,\n      sentenceDelimiters: '',\n      wordDelimiters: '',\n      characterCount: true,\n      wordCount: false\n    }\n  },\n  {\n    title: 'Text Statistics with Characters and Words Frequencies',\n    description:\n      'This example shows basic text statistics with characters and words frequencies.',\n    sampleText: `The Great Barrier Reef is the world's largest coral reef system, located off the coast of Australia. It consists of over 2,900 individual reefs and 900 islands. The reef is home to thousands of species of marine life, including fish, sea turtles, sharks, and dolphins. It is also a popular tourist destination, attracting millions of visitors every year. However, the reef is facing many threats, including climate change, pollution, and overfishing. Conservation efforts are being made to protect this unique and valuable ecosystem for future generations.`,\n    sampleResult: `Text Statistics\n==================\nCharacters: 556\nWords: 87\nLines: 1\nSentences: 1\nParagraphs: 1\n\nWords Frequency\n==================\n2: 1 (1.15%)\n900: 2 (2.30%)\nthe: 5 (5.75%)\nof: 5 (5.75%)\nreef: 4 (4.60%)\nis: 4 (4.60%)\nand: 4 (4.60%)\nit: 2 (2.30%)\nto: 2 (2.30%)\nincluding: 2 (2.30%)\ngreat: 1 (1.15%)\nbarrier: 1 (1.15%)\nworld's: 1 (1.15%)\nlargest: 1 (1.15%)\ncoral: 1 (1.15%)\nsystem: 1 (1.15%)\nlocated: 1 (1.15%)\noff: 1 (1.15%)\ncoast: 1 (1.15%)\naustralia: 1 (1.15%)\nconsists: 1 (1.15%)\nover: 1 (1.15%)\nindividual: 1 (1.15%)\nreefs: 1 (1.15%)\nislands: 1 (1.15%)\nhome: 1 (1.15%)\nthousands: 1 (1.15%)\nspecies: 1 (1.15%)\nmarine: 1 (1.15%)\nlife: 1 (1.15%)\nfish: 1 (1.15%)\nsea: 1 (1.15%)\nturtles: 1 (1.15%)\nsharks: 1 (1.15%)\ndolphins: 1 (1.15%)\nalso: 1 (1.15%)\na: 1 (1.15%)\npopular: 1 (1.15%)\ntourist: 1 (1.15%)\ndestination: 1 (1.15%)\nattracting: 1 (1.15%)\nmillions: 1 (1.15%)\nvisitors: 1 (1.15%)\nevery: 1 (1.15%)\nyear: 1 (1.15%)\nhowever: 1 (1.15%)\nfacing: 1 (1.15%)\nmany: 1 (1.15%)\nthreats: 1 (1.15%)\nclimate: 1 (1.15%)\nchange: 1 (1.15%)\npollution: 1 (1.15%)\noverfishing: 1 (1.15%)\nconservation: 1 (1.15%)\nefforts: 1 (1.15%)\nare: 1 (1.15%)\nbeing: 1 (1.15%)\nmade: 1 (1.15%)\nprotect: 1 (1.15%)\nthis: 1 (1.15%)\nunique: 1 (1.15%)\nvaluable: 1 (1.15%)\necosystem: 1 (1.15%)\nfor: 1 (1.15%)\nfuture: 1 (1.15%)\ngenerations: 1 (1.15%)\n\nCharacters Frequency\n==================\n0: 4 (0.72%)\n2: 1 (0.18%)\n9: 2 (0.36%)\n␣: 85 (15.29%)\ne: 51 (9.17%)\ni: 40 (7.19%)\ns: 40 (7.19%)\nt: 39 (7.01%)\na: 37 (6.65%)\no: 34 (6.12%)\nr: 33 (5.94%)\nn: 29 (5.22%)\nl: 21 (3.78%)\nf: 20 (3.60%)\nh: 15 (2.70%)\nd: 15 (2.70%)\nc: 14 (2.52%)\nu: 14 (2.52%)\n,: 11 (1.98%)\ng: 10 (1.80%)\nm: 8 (1.44%)\nv: 8 (1.44%)\n.: 6 (1.08%)\np: 6 (1.08%)\ny: 5 (0.90%)\nb: 3 (0.54%)\nw: 2 (0.36%)\n': 1 (0.18%)\nk: 1 (0.18%)\nq: 1 (0.18%)`,\n    sampleOptions: {\n      emptyLines: false,\n      sentenceDelimiters: '',\n      wordDelimiters: '',\n      characterCount: true,\n      wordCount: true\n    }\n  }\n];\n\nexport default function Truncate({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  function compute(initialValues: InitialValuesType, input: string) {\n    setResult(textStatistics(input, initialValues));\n  }\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('statistic.delimitersOptions'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.sentenceDelimiters}\n            onOwnChange={(val) => updateField('sentenceDelimiters', val)}\n            placeholder={t('statistic.sentenceDelimitersPlaceholder')}\n            description={t('statistic.sentenceDelimitersDescription')}\n          />\n          <TextFieldWithDesc\n            value={values.wordDelimiters}\n            onOwnChange={(val) => updateField('wordDelimiters', val)}\n            placeholder={t('statistic.wordDelimitersPlaceholder')}\n            description={t('statistic.wordDelimitersDescription')}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('statistic.statisticsOptions'),\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            checked={values.wordCount}\n            onChange={(value) => updateField('wordCount', value)}\n            title={t('statistic.wordFrequencyAnalysis')}\n            description={t('statistic.wordFrequencyAnalysisDescription')}\n          />\n          <CheckboxWithDesc\n            checked={values.characterCount}\n            onChange={(value) => updateField('characterCount', value)}\n            title={t('statistic.characterFrequencyAnalysis')}\n            description={t('statistic.characterFrequencyAnalysisDescription')}\n          />\n          <CheckboxWithDesc\n            checked={values.emptyLines}\n            onChange={(value) => updateField('emptyLines', value)}\n            title={t('statistic.includeEmptyLines')}\n            description={t('statistic.includeEmptyLinesDescription')}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('statistic.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('statistic.resultTitle')} value={result} />\n      }\n      toolInfo={{\n        title: t('statistic.toolInfo.title', { title }),\n        description: longDescription\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/statistic/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'statistics',\n\n  icon: 'fluent:document-landscape-data-24-filled',\n\n  keywords: ['text', 'statistics', 'count', 'lines', 'words', 'characters'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:statistic.title',\n    description: 'string:statistic.description',\n    shortDescription: 'string:statistic.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/statistic/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { TopItemsList } from '../../list/find-most-popular/service';\n\nfunction countLines(text: string, options: InitialValuesType): number {\n  const numberofLines = options.emptyLines\n    ? text.split('\\n').length\n    : text.split('\\n').filter((line) => line.trim() !== '').length;\n\n  return numberofLines;\n}\n\nfunction countCharacters(text: string): number {\n  return text.length;\n}\n\nfunction countSentences(text: string, options: InitialValuesType): number {\n  const sentenceDelimiters = options.sentenceDelimiters\n    ? options.sentenceDelimiters.split(',').map((s) => s.trim())\n    : ['.', '!', '?', '...'];\n\n  const regex = new RegExp(`[${sentenceDelimiters.join('')}]`, 'g');\n  const sentences = text\n    .split(regex)\n    .filter((sentence) => sentence.trim() !== '');\n  return sentences.length;\n}\n\nfunction wordsStats(\n  text: string,\n  options: InitialValuesType\n): [number, string] {\n  const defaultDelimiters = `\\\\s.,;:!?\"“”«»()…`;\n  const wordDelimiters = options.wordDelimiters || defaultDelimiters;\n  const regex = new RegExp(`[${wordDelimiters}]`, 'gu');\n  const words = text.split(regex).filter((word) => word.trim() !== '');\n\n  const wordsFrequency = TopItemsList(\n    'regex',\n    'count',\n    'percentage',\n    '',\n    words,\n    false,\n    true,\n    false\n  );\n\n  return options.wordCount\n    ? [words.length, wordsFrequency]\n    : [words.length, ''];\n}\n\nfunction countParagraphs(text: string): number {\n  return text\n    .split(/\\r?\\n\\s*\\r?\\n/)\n    .filter((paragraph) => paragraph.trim() !== '').length;\n}\n\nfunction charactersStatistic(text: string, options: InitialValuesType): string {\n  if (!options.characterCount) return '';\n  const result = TopItemsList(\n    'symbol',\n    'count',\n    'percentage',\n    '',\n    text,\n    true,\n    true,\n    false\n  );\n  return result;\n}\n\nexport function textStatistics(\n  input: string,\n  options: InitialValuesType\n): string {\n  if (!input) return '';\n\n  const numberofLines = countLines(input, options);\n  const numberofCharacters = countCharacters(input);\n  const numberofSentences = countSentences(input, options);\n  const [numberofWords, wordsFrequency] = wordsStats(input, options);\n  const numberofParagraphs = countParagraphs(input);\n  const characterStats = charactersStatistic(input, options);\n\n  const stats = `Text Statistics\n==================\nCharacters: ${numberofCharacters}\nWords: ${numberofWords}\nLines: ${numberofLines}\nSentences: ${numberofSentences}\nParagraphs: ${numberofParagraphs}`;\n\n  const charStats = `Characters Frequency\n==================\n${characterStats}`;\n\n  const wordStatsOutput = `Words Frequency\n==================\n${wordsFrequency}`;\n\n  let result = stats;\n  if (options.wordCount) result += `\\n\\n${wordStatsOutput}`;\n  if (options.characterCount) result += `\\n\\n${charStats}`;\n\n  return result;\n}\n"
  },
  {
    "path": "src/pages/tools/string/statistic/types.ts",
    "content": "export type InitialValuesType = {\n  emptyLines: boolean;\n  sentenceDelimiters: string;\n  wordDelimiters: string;\n  characterCount: boolean;\n  wordCount: boolean;\n};\n"
  },
  {
    "path": "src/pages/tools/string/text-replacer/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport { useState } from 'react';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { replaceText } from './service';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport { initialValues, InitialValuesType } from './initialValues';\nimport ToolContent from '@components/ToolContent';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Replace specific word in text',\n    description:\n      'In this example we will replace the word \"hello\" with the word \"hi\". This example doesn\\'t use regular expressions.',\n    sampleText: 'hello, how are you today? hello!',\n    sampleResult: 'hi, how are you today? hi!',\n    sampleOptions: {\n      textToReplace: 'hello, how are you today? hello!',\n      searchValue: 'hello',\n      searchRegexp: '',\n      replaceValue: 'hi',\n      mode: 'text'\n    }\n  },\n  {\n    title: 'Replace all numbers in text',\n    description:\n      'In this example we will replace all numbers in numbers with * using regexp. In the output we will get text with numbers replaced with *.',\n    sampleText: 'The price is 100$, and the discount is 20%.',\n    sampleResult: 'The price is X$, and the discount is X%.',\n    sampleOptions: {\n      textToReplace: 'The price is 100$, and the discount is 20%.',\n      searchValue: '',\n      searchRegexp: '/\\\\d+/g',\n      replaceValue: '*',\n      mode: 'regexp'\n    }\n  },\n  {\n    title: 'Replace all dates in text',\n    description:\n      'In this example we will replace all dates in the format YYYY-MM-DD with the word DATE using regexp. The output will have all the dates replaced with the word DATE.',\n    sampleText:\n      'The event will take place on 2025-03-10, and the deadline is 2025-03-15.',\n    sampleResult:\n      'The event will take place on DATE, and the deadline is DATE.',\n    sampleOptions: {\n      textToReplace:\n        'The event will take place on 2025-03-10, and the deadline is 2025-03-15.',\n      searchValue: '',\n      searchRegexp: '/\\\\d{4}-\\\\d{2}-\\\\d{2}/g',\n      replaceValue: 'DATE',\n      mode: 'regexp'\n    }\n  }\n];\n\nexport default function Replacer({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  function compute(optionsValues: InitialValuesType, input: string) {\n    setResult(replaceText(optionsValues, input));\n  }\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('textReplacer.searchText'),\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('mode', 'text')}\n            checked={values.mode === 'text'}\n            title={t('textReplacer.findPatternInText')}\n          />\n          <TextFieldWithDesc\n            description={t('textReplacer.searchPatternDescription')}\n            value={values.searchValue}\n            onOwnChange={(val) => updateField('searchValue', val)}\n            type={'text'}\n          />\n          <SimpleRadio\n            onClick={() => updateField('mode', 'regexp')}\n            checked={values.mode === 'regexp'}\n            title={t('textReplacer.findPatternUsingRegexp')}\n          />\n          <TextFieldWithDesc\n            description={t('textReplacer.regexpDescription')}\n            value={values.searchRegexp}\n            onOwnChange={(val) => updateField('searchRegexp', val)}\n            type={'text'}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('textReplacer.replaceText'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            description={t('textReplacer.replacePatternDescription')}\n            placeholder={t('textReplacer.newTextPlaceholder')}\n            value={values.replaceValue}\n            onOwnChange={(val) => updateField('replaceValue', val)}\n            type={'text'}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('textReplacer.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('textReplacer.resultTitle')} value={result} />\n      }\n      toolInfo={{\n        title: t('textReplacer.toolInfo.title'),\n        description: t('textReplacer.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/text-replacer/initialValues.ts",
    "content": "export type InitialValuesType = {\n  textToReplace: string;\n  searchValue: string;\n  searchRegexp: string;\n  replaceValue: string;\n  mode: 'text' | 'regexp';\n};\n\nexport const initialValues: InitialValuesType = {\n  textToReplace: '',\n  searchValue: '',\n  searchRegexp: '',\n  replaceValue: '',\n  mode: 'text'\n};\n"
  },
  {
    "path": "src/pages/tools/string/text-replacer/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  i18n: {\n    name: 'string:textReplacer.title',\n    description: 'string:textReplacer.description',\n    shortDescription: 'string:textReplacer.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  },\n\n  path: 'replacer',\n\n  icon: 'material-symbols-light:find-replace',\n\n  keywords: ['text', 'replace'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/string/text-replacer/replaceText.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { replaceText } from './service';\nimport { initialValues } from './initialValues';\n\ndescribe('replaceText function (text mode)', () => {\n  const mode = 'text';\n\n  it('should replace the word in the text correctly', () => {\n    const text = 'Lorem ipsum odor amet, consectetuer adipiscing elit.';\n    const searchValue = 'ipsum';\n    const replaceValue = 'vitae';\n    const result = replaceText(\n      { ...initialValues, searchValue, replaceValue, mode },\n      text\n    );\n    expect(result).toBe('Lorem vitae odor amet, consectetuer adipiscing elit.');\n  });\n\n  it('should replace letters in the text correctly', () => {\n    const text =\n      'Luctus penatibus montes elementum lacus mus vivamus lacus laoreet.';\n    const searchValue = 'e';\n    const replaceValue = 'u';\n    const result = replaceText(\n      { ...initialValues, searchValue, replaceValue, mode },\n      text\n    );\n    expect(result).toBe(\n      'Luctus punatibus montus ulumuntum lacus mus vivamus lacus laoruut.'\n    );\n  });\n\n  it('should return the original text if one of the required arguments is an empty string', () => {\n    const text =\n      'Nostra netus quisque ornare neque dolor sem nostra venenatis.';\n    expect(\n      replaceText(\n        { ...initialValues, searchValue: '', replaceValue: 'test', mode },\n        text\n      )\n    ).toBe('Nostra netus quisque ornare neque dolor sem nostra venenatis.');\n    expect(\n      replaceText(\n        { ...initialValues, searchValue: 'ornare', replaceValue: 'test', mode },\n        ''\n      )\n    ).toBe('');\n  });\n\n  it('should replace multiple occurrences of the word correctly', () => {\n    const text = 'apple orange apple banana apple';\n    const searchValue = 'apple';\n    const replaceValue = 'grape';\n    const result = replaceText(\n      { ...initialValues, searchValue, replaceValue, mode },\n      text\n    );\n    expect(result).toBe('grape orange grape banana grape');\n  });\n\n  it('should return the original text if the replace value is an empty string', () => {\n    const text = 'apple orange apple banana apple';\n    const searchValue = 'apple';\n    const replaceValue = '';\n    const result = replaceText(\n      { ...initialValues, searchValue, replaceValue, mode },\n      text\n    );\n    expect(result).toBe(' orange  banana ');\n  });\n\n  it('should return the original text if the search value is not found', () => {\n    const text = 'apple orange banana';\n    const searchValue = 'grape';\n    const replaceValue = 'melon';\n    const result = replaceText(\n      { ...initialValues, searchValue, replaceValue, mode },\n      text\n    );\n    expect(result).toBe('apple orange banana');\n  });\n});\n\ndescribe('replaceText function (regexp mode)', () => {\n  const mode = 'regexp';\n\n  it('should replace a word in text using regexp correctly', () => {\n    const text = 'Egestas lobortis facilisi convallis rhoncus nunc.';\n    const searchRegexp = '/nunc/';\n    const replaceValue = 'hello';\n    const result = replaceText(\n      { ...initialValues, searchRegexp, replaceValue, mode },\n      text\n    );\n    expect(result).toBe('Egestas lobortis facilisi convallis rhoncus hello.');\n  });\n\n  it('should replace all words in the text with regexp correctly', () => {\n    const text =\n      'Parturient porta ultricies tellus ultricies suscipit quisque torquent.';\n    const searchRegexp = '/ultricies/g';\n    const replaceValue = 'hello';\n    const result = replaceText(\n      { ...initialValues, searchRegexp, replaceValue, mode },\n      text\n    );\n    expect(result).toBe(\n      'Parturient porta hello tellus hello suscipit quisque torquent.'\n    );\n  });\n\n  it('should replace words in text with regexp using alternation operator correctly', () => {\n    const text =\n      'Commodo maximus nullam dis placerat fermentum curabitur semper.';\n    const searchRegexp = '/nullam|fermentum/g';\n    const replaceValue = 'test';\n    const result = replaceText(\n      { ...initialValues, searchRegexp, replaceValue, mode },\n      text\n    );\n    expect(result).toBe(\n      'Commodo maximus test dis placerat test curabitur semper.'\n    );\n  });\n\n  it('should return the original text when passed an invalid regexp', () => {\n    const text =\n      'Commodo maximus nullam dis placerat fermentum curabitur semper.';\n    const searchRegexp = '/(/';\n    const replaceValue = 'test';\n    const result = replaceText(\n      { ...initialValues, searchRegexp, replaceValue, mode },\n      text\n    );\n    expect(result).toBe(\n      'Commodo maximus nullam dis placerat fermentum curabitur semper.'\n    );\n  });\n\n  it('should remove brackets from text correctly using regexp', () => {\n    const text =\n      'Porta nulla (magna) lectus, [taciti] habitant nunc urna maximus metus.';\n    const searchRegexp = '/[()\\\\[\\\\]]/g';\n    const replaceValue = '';\n    const result = replaceText(\n      { ...initialValues, searchRegexp, replaceValue, mode },\n      text\n    );\n    expect(result).toBe(\n      'Porta nulla magna lectus, taciti habitant nunc urna maximus metus.'\n    );\n  });\n\n  it('should replace case-insensitive words correctly', () => {\n    const text = 'Porta cras ad laoreet porttitor feRmeNtum consectetur?';\n    const searchRegexp = '/porta|fermentum/gi';\n    const replaceValue = 'test';\n    const result = replaceText(\n      { ...initialValues, searchRegexp, replaceValue, mode },\n      text\n    );\n    expect(result).toBe('test cras ad laoreet porttitor test consectetur?');\n  });\n\n  it('should replace words with digits and symbols correctly', () => {\n    const text = 'The price is 100$, and the discount is 20%.';\n    const searchRegexp = '/\\\\d+/g';\n    const replaceValue = 'X';\n    const result = replaceText(\n      { ...initialValues, searchRegexp, replaceValue, mode },\n      text\n    );\n    expect(result).toBe('The price is X$, and the discount is X%.');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/text-replacer/service.ts",
    "content": "import { InitialValuesType } from './initialValues';\n\nfunction isFieldsEmpty(textField: string, searchField: string) {\n  return !textField.trim() || !searchField.trim();\n}\n\nexport function replaceText(options: InitialValuesType, text: string) {\n  const { searchValue, searchRegexp, replaceValue, mode } = options;\n\n  switch (mode) {\n    case 'text':\n      if (isFieldsEmpty(text, searchValue)) return text;\n      return text.replaceAll(searchValue, replaceValue);\n    case 'regexp':\n      if (isFieldsEmpty(text, searchRegexp)) return text;\n      return replaceTextWithRegexp(text, searchRegexp, replaceValue);\n  }\n}\n\nfunction replaceTextWithRegexp(\n  text: string,\n  searchRegexp: string,\n  replaceValue: string\n) {\n  try {\n    const match = searchRegexp.match(/^\\/(.*)\\/([a-z]*)$/i);\n\n    if (match) {\n      // Input is in /pattern/flags format\n      const [, pattern, flags] = match;\n      return text.replace(new RegExp(pattern, flags), replaceValue);\n    } else {\n      // Input is a raw pattern - don't escape it\n      return text.replace(new RegExp(searchRegexp, 'g'), replaceValue);\n    }\n  } catch (err) {\n    // console.error('Invalid regular expression:', err);\n    return text;\n  }\n}\n"
  },
  {
    "path": "src/pages/tools/string/to-morse/index.tsx",
    "content": "import ToolContent from '@components/ToolContent';\nimport React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { compute } from './service';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  dotSymbol: '.',\n  dashSymbol: '-'\n};\n\nexport default function ToMorse() {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n  const computeOptions = (optionsValues: typeof initialValues, input: any) => {\n    const { dotSymbol, dashSymbol } = optionsValues;\n    setResult(compute(input, dotSymbol, dashSymbol));\n  };\n\n  return (\n    <ToolContent\n      title={t('toMorse.title')}\n      initialValues={initialValues}\n      compute={computeOptions}\n      input={input}\n      setInput={setInput}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={\n        <ToolTextResult title={t('toMorse.resultTitle')} value={result} />\n      }\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('toMorse.shortSignal'),\n          component: (\n            <TextFieldWithDesc\n              description={t('toMorse.dotSymbolDescription')}\n              value={values.dotSymbol}\n              onOwnChange={(val) => updateField('dotSymbol', val)}\n            />\n          )\n        },\n        {\n          title: t('toMorse.longSignal'),\n          component: (\n            <TextFieldWithDesc\n              description={t('toMorse.dashSymbolDescription')}\n              value={values.dashSymbol}\n              onOwnChange={(val) => updateField('dashSymbol', val)}\n            />\n          )\n        }\n      ]}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/to-morse/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('string', {\n  path: 'to-morse',\n  icon: 'arcticons:morse',\n  keywords: [],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:toMorse.title',\n    description: 'string:toMorse.description',\n    shortDescription: 'string:toMorse.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/to-morse/service.ts",
    "content": "import { encode } from 'morsee';\n\nexport const compute = (\n  input: string,\n  dotSymbol: string,\n  dashSymbol: string\n): string => {\n  return encode(input).replaceAll('.', dotSymbol).replaceAll('-', dashSymbol);\n};\n"
  },
  {
    "path": "src/pages/tools/string/to-morse/to-morse.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { compute } from './service';\n\ndescribe('compute function', () => {\n  it('should replace dots and dashes with specified symbols', () => {\n    const input = 'test';\n    const dotSymbol = '*';\n    const dashSymbol = '#';\n    const result = compute(input, dotSymbol, dashSymbol);\n    const expected = '# * *** #';\n    expect(result).toBe(expected);\n  });\n\n  it('should return an empty string for empty input', () => {\n    const input = '';\n    const dotSymbol = '*';\n    const dashSymbol = '#';\n    const result = compute(input, dotSymbol, dashSymbol);\n    expect(result).toBe('');\n  });\n\n  // Test case 3: Special characters handling\n  it('should handle input with special characters', () => {\n    const input = 'hello, world!';\n    const dotSymbol = '*';\n    const dashSymbol = '#';\n    const result = compute(input, dotSymbol, dashSymbol);\n    const expected =\n      '**** * *#** *#** ### ##**## / *## ### *#* *#** #** #*#*##';\n    expect(result).toBe(expected);\n  });\n\n  it('should work with different symbols for dots and dashes', () => {\n    const input = 'morse';\n    const dotSymbol = '!';\n    const dashSymbol = '@';\n    const result = compute(input, dotSymbol, dashSymbol);\n    const expected = '@@ @@@ !@! !!! !';\n    expect(result).toBe(expected);\n  });\n\n  it('should handle numeric input correctly', () => {\n    const input = '12345';\n    const dotSymbol = '*';\n    const dashSymbol = '#';\n    const result = compute(input, dotSymbol, dashSymbol);\n    const expected = '*#### **### ***## ****# *****'; // This depends on how \"12345\" is encoded in morse code\n    expect(result).toBe(expected);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/truncate/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport { useState } from 'react';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { truncateText } from './service';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport { initialValues, InitialValuesType } from './initialValues';\nimport ToolContent from '@components/ToolContent';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { useTranslation } from 'react-i18next';\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Basic Truncation on the Right',\n    description: 'Truncate text from the right side based on max length.',\n    sampleText: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',\n    sampleResult: 'Lorem ipsum dolor...',\n    sampleOptions: {\n      ...initialValues,\n      textToTruncate:\n        'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',\n      maxLength: '20',\n      truncationSide: 'right',\n      addIndicator: true,\n      indicator: '...'\n    }\n  },\n  {\n    title: 'Truncation on the Left with Indicator',\n    description: 'Truncate text from the left side and add an indicator.',\n    sampleText: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',\n    sampleResult: '...is dolor sit amet, consectetur adipiscing elit.',\n    sampleOptions: {\n      ...initialValues,\n      textToTruncate:\n        'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',\n      maxLength: '40',\n      truncationSide: 'left',\n      addIndicator: true,\n      indicator: '...'\n    }\n  },\n  {\n    title: 'Multi-line Truncation with Indicator',\n    description:\n      'Truncate text line by line, adding an indicator to each line.',\n    sampleText: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.`,\n    sampleResult: `Lorem ipsum dolor sit amet, consectetur...\nUt enim ad minim veniam, quis nostrud...\nDuis aute irure dolor in reprehenderit...`,\n    sampleOptions: {\n      ...initialValues,\n      textToTruncate: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.`,\n      maxLength: '50',\n      lineByLine: true,\n      addIndicator: true,\n      indicator: '...'\n    }\n  }\n];\n\nexport default function Truncate({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  function compute(optionsValues: InitialValuesType, input: string) {\n    setResult(truncateText(optionsValues, input));\n  }\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('truncate.truncationSide'),\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('truncationSide', 'right')}\n            checked={values.truncationSide === 'right'}\n            title={t('truncate.rightSideTruncation')}\n            description={t('truncate.rightSideDescription')}\n          />\n          <SimpleRadio\n            onClick={() => updateField('truncationSide', 'left')}\n            checked={values.truncationSide === 'left'}\n            title={t('truncate.leftSideTruncation')}\n            description={t('truncate.leftSideDescription')}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('truncate.lengthAndLines'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            description={t('truncate.maxLengthDescription')}\n            placeholder={t('truncate.numberPlaceholder')}\n            value={values.maxLength}\n            onOwnChange={(val) => updateField('maxLength', val)}\n            type={'number'}\n          />\n          <CheckboxWithDesc\n            onChange={(val) => updateField('lineByLine', val)}\n            checked={values.lineByLine}\n            title={t('truncate.lineByLineTruncating')}\n            description={t('truncate.lineByLineDescription')}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('truncate.suffixAndAffix'),\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            onChange={(val) => updateField('addIndicator', val)}\n            checked={values.addIndicator}\n            title={t('truncate.addTruncationIndicator')}\n            description={''}\n          />\n          <TextFieldWithDesc\n            description={t('truncate.indicatorDescription')}\n            placeholder={t('truncate.charactersPlaceholder')}\n            value={values.indicator}\n            onOwnChange={(val) => updateField('indicator', val)}\n            type={'text'}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('truncate.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('truncate.resultTitle')} value={result} />\n      }\n      toolInfo={{\n        title: t('truncate.toolInfo.title'),\n        description: t('truncate.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/truncate/initialValues.ts",
    "content": "export type truncationSideType = 'right' | 'left';\n\nexport type InitialValuesType = {\n  textToTruncate: string;\n  truncationSide: truncationSideType;\n  maxLength: string;\n  lineByLine: boolean;\n  addIndicator: boolean;\n  indicator: string;\n};\n\nexport const initialValues: InitialValuesType = {\n  textToTruncate: '',\n  truncationSide: 'right',\n  maxLength: '15',\n  lineByLine: false,\n  addIndicator: false,\n  indicator: ''\n};\n"
  },
  {
    "path": "src/pages/tools/string/truncate/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'truncate',\n  icon: 'material-symbols-light:content-cut',\n\n  keywords: ['truncate'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:truncate.title',\n    description: 'string:truncate.description',\n    shortDescription: 'string:truncate.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/truncate/service.ts",
    "content": "import { InitialValuesType, truncationSideType } from './initialValues';\n\nexport function truncateText(options: InitialValuesType, text: string) {\n  const { truncationSide, maxLength, lineByLine, addIndicator, indicator } =\n    options;\n\n  const parsedMaxLength = parseInt(maxLength) || 0;\n\n  if (parsedMaxLength < 0) {\n    throw new Error('Length value cannot be negative');\n  }\n\n  const truncate =\n    truncationSide === 'right' ? truncateFromRight : truncateFromLeft;\n\n  return lineByLine\n    ? text\n        .split('\\n')\n        .map((line) =>\n          truncate(\n            line,\n            parsedMaxLength,\n            addIndicator,\n            indicator,\n            truncationSide\n          )\n        )\n        .join('\\n')\n    : truncate(text, parsedMaxLength, addIndicator, indicator, truncationSide);\n}\n\nfunction truncateFromRight(\n  text: string,\n  maxLength: number,\n  addIndicator: boolean,\n  indicator: string,\n  truncationSide: truncationSideType\n) {\n  const result = text.slice(0, maxLength);\n\n  return addIndicator\n    ? addIndicatorToText(result, indicator, truncationSide)\n    : result;\n}\n\nfunction truncateFromLeft(\n  text: string,\n  maxLength: number,\n  addIndicator: boolean,\n  indicator: string,\n  truncationSide: truncationSideType\n) {\n  const result = text.slice(-maxLength);\n\n  return addIndicator\n    ? addIndicatorToText(result, indicator, truncationSide)\n    : result;\n}\n\nfunction addIndicatorToText(\n  text: string,\n  indicator: string,\n  truncationSide: truncationSideType\n) {\n  if (indicator.length > text.length && text.length) {\n    throw new Error('Indicator length is greater than truncation length');\n  }\n\n  if (!text.length) {\n    return '';\n  }\n\n  switch (truncationSide) {\n    case 'right': {\n      const result = text.slice(0, text.length - indicator.length);\n      return `${result}${indicator}`;\n    }\n\n    case 'left': {\n      const result = text.slice(-text.length + indicator.length);\n      return `${indicator}${result}`;\n    }\n  }\n}\n"
  },
  {
    "path": "src/pages/tools/string/truncate/truncateText.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { truncateText } from './service';\nimport { initialValues } from './initialValues';\n\ndescribe('repeatText function (normal mode)', () => {\n  const text =\n    'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';\n\n  it('should truncate the text correctly on the right side', () => {\n    const maxLength = '30';\n    const result = truncateText({ ...initialValues, maxLength }, text);\n    expect(result).toBe('Lorem ipsum dolor sit amet, co');\n  });\n\n  it('should truncate the text correctly on the left side', () => {\n    const maxLength = '30';\n    const truncationSide = 'left';\n    const result = truncateText(\n      { ...initialValues, maxLength, truncationSide },\n      text\n    );\n    expect(result).toBe('labore et dolore magna aliqua.');\n  });\n\n  it('should truncate the text and add the indicator correctly on the right side', () => {\n    const maxLength = '24';\n    const addIndicator = true;\n    const indicator = '...';\n    const result = truncateText(\n      { ...initialValues, maxLength, addIndicator, indicator },\n      text\n    );\n    expect(result).toBe('Lorem ipsum dolor sit...');\n  });\n\n  it('should truncate the text and add the indicator correctly on the left side', () => {\n    const maxLength = '23';\n    const truncationSide = 'left';\n    const addIndicator = true;\n    const indicator = '...';\n    const result = truncateText(\n      { ...initialValues, maxLength, truncationSide, addIndicator, indicator },\n      text\n    );\n    expect(result).toBe('...dolore magna aliqua.');\n  });\n\n  it('should throw an error if maxLength is less than zero', () => {\n    const maxLength = '-1';\n    expect(() =>\n      truncateText({ ...initialValues, maxLength }, text)\n    ).toThrowError('Length value cannot be negative');\n  });\n\n  it('should throw an error if the indicator length is greater than the truncate length', () => {\n    const maxLength = '10';\n    const addIndicator = true;\n    const indicator = '.'.repeat(11);\n    expect(() =>\n      truncateText(\n        { ...initialValues, maxLength, addIndicator, indicator },\n        text\n      )\n    ).toThrowError('Indicator length is greater than truncation length');\n  });\n});\n\ndescribe('repeatText function (line by line mode)', () => {\n  const text = `\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.`;\n  const maxLength = '45';\n\n  it('should truncate the multi-line text correctly on the right side', () => {\n    const lineByLine = true;\n    const result = truncateText(\n      { ...initialValues, maxLength, lineByLine },\n      text\n    );\n    expect(result).toBe(`\nLorem ipsum dolor sit amet, consectetur adipi\nUt enim ad minim veniam, quis nostrud exercit\nDuis aute irure dolor in reprehenderit in vol`);\n  });\n\n  it('should truncate the multi-line text correctly on the left side', () => {\n    const truncationSide = 'left';\n    const lineByLine = true;\n    const result = truncateText(\n      { ...initialValues, maxLength, truncationSide, lineByLine },\n      text\n    );\n    expect(result).toBe(`\n incididunt ut labore et dolore magna aliqua.\noris nisi ut aliquip ex ea commodo consequat.\n esse cillum dolore eu fugiat nulla pariatur.`);\n  });\n\n  it('should truncate the multi-line and add the indicator text correctly on the right side', () => {\n    const lineByLine = true;\n    const addIndicator = true;\n    const indicator = '...';\n    const result = truncateText(\n      { ...initialValues, maxLength, lineByLine, addIndicator, indicator },\n      text\n    );\n    expect(result).toBe(`\nLorem ipsum dolor sit amet, consectetur ad...\nUt enim ad minim veniam, quis nostrud exer...\nDuis aute irure dolor in reprehenderit in ...`);\n  });\n\n  it('should truncate the multi-line and add the indicator text correctly on the left side', () => {\n    const lineByLine = true;\n    const truncationSide = 'left';\n    const addIndicator = true;\n    const indicator = '...';\n    const result = truncateText(\n      {\n        ...initialValues,\n        maxLength,\n        truncationSide,\n        lineByLine,\n        addIndicator,\n        indicator\n      },\n      text\n    );\n    expect(result).toBe(`\n...cididunt ut labore et dolore magna aliqua.\n...s nisi ut aliquip ex ea commodo consequat.\n...se cillum dolore eu fugiat nulla pariatur.`);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/unicode/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { unicode } from './service';\nimport { InitialValuesType } from './types';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  mode: 'encode',\n  uppercase: false\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Encode to Unicode Escape',\n    description: 'Encode plain text to Unicode escape sequences.',\n    sampleText: 'Hello, World!',\n    sampleResult:\n      '\\\\u0048\\\\u0065\\\\u006c\\\\u006c\\\\u006f\\\\u002c\\\\u0020\\\\u0057\\\\u006f\\\\u0072\\\\u006c\\\\u0064\\\\u0021',\n    sampleOptions: {\n      mode: 'encode',\n      uppercase: false\n    }\n  },\n  {\n    title: 'Encode to Unicode Escape (Uppercase)',\n    description: 'Encode plain text to uppercase Unicode escape sequences.',\n    sampleText: 'Hello, World!',\n    sampleResult:\n      '\\\\u0048\\\\u0065\\\\u006c\\\\u006c\\\\u006f\\\\u002c\\\\u0020\\\\u0057\\\\u006f\\\\u0072\\\\u006c\\\\u0064\\\\u0021'.toUpperCase(),\n    sampleOptions: {\n      mode: 'encode',\n      uppercase: true\n    }\n  },\n  {\n    title: 'Decode Unicode Escape',\n    description: 'Decode Unicode escape sequences back to plain text.',\n    sampleText:\n      '\\\\u0048\\\\u0065\\\\u006c\\\\u006c\\\\u006f\\\\u002c\\\\u0020\\\\u0057\\\\u006f\\\\u0072\\\\u006c\\\\u0064\\\\u0021',\n    sampleResult: 'Hello, World!',\n    sampleOptions: {\n      mode: 'decode',\n      uppercase: false\n    }\n  }\n];\nexport default function Unicode({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    setResult(unicode(input, values));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('unicode.optionsTitle'),\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('mode', 'encode')}\n            checked={values.mode === 'encode'}\n            title={t('unicode.encode')}\n          />\n          <SimpleRadio\n            onClick={() => updateField('mode', 'decode')}\n            checked={values.mode === 'decode'}\n            title={t('unicode.decode')}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('unicode.caseOptionsTitle'),\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            checked={values.uppercase}\n            onChange={(value) => updateField('uppercase', value)}\n            title={t('unicode.uppercase')}\n          />\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput\n          value={input}\n          onChange={setInput}\n          title={t('unicode.inputTitle')}\n        />\n      }\n      resultComponent={\n        <ToolTextResult value={result} title={t('unicode.resultTitle')} />\n      }\n      initialValues={initialValues}\n      exampleCards={exampleCards}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('unicode.toolInfo.title'),\n        description: t('unicode.toolInfo.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/unicode/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  i18n: {\n    name: 'string:unicode.title',\n    description: 'string:unicode.description',\n    shortDescription: 'string:unicode.shortDescription'\n  },\n  path: 'unicode',\n  icon: 'mdi:unicode',\n  keywords: ['unicode', 'encode', 'decode', 'escape', 'text'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/string/unicode/service.ts",
    "content": "import { InitialValuesType } from './types';\n\nexport function unicode(input: string, options: InitialValuesType): string {\n  if (!input) return '';\n  if (options.mode === 'encode') {\n    let result = '';\n    for (let i = 0; i < input.length; i++) {\n      let hex = input.charCodeAt(i).toString(16);\n      hex = ('0000' + hex).slice(-4);\n      if (options.uppercase) {\n        hex = hex.toUpperCase();\n      }\n      result += '\\\\u' + hex;\n    }\n    return result;\n  } else {\n    return input.replace(/\\\\u([\\dA-Fa-f]{4})/g, (match, grp) => {\n      return String.fromCharCode(parseInt(grp, 16));\n    });\n  }\n}\n"
  },
  {
    "path": "src/pages/tools/string/unicode/types.ts",
    "content": "export type InitialValuesType = {\n  mode: 'encode' | 'decode';\n  uppercase: boolean;\n};\n"
  },
  {
    "path": "src/pages/tools/string/unicode/unicode.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { unicode } from './service';\n\ndescribe('unicode', () => {\n  it('should encode an English string to lowercase hex correctly', () => {\n    const input = 'Hello, World!';\n    const result = unicode(input, { mode: 'encode', uppercase: false });\n    expect(result).toBe(\n      '\\\\u0048\\\\u0065\\\\u006c\\\\u006c\\\\u006f\\\\u002c\\\\u0020\\\\u0057\\\\u006f\\\\u0072\\\\u006c\\\\u0064\\\\u0021'\n    );\n  });\n\n  it('should encode an English string to uppercase hex correctly', () => {\n    const input = 'Hello, World!';\n    const result = unicode(input, { mode: 'encode', uppercase: true });\n    expect(result).toBe(\n      '\\\\u0048\\\\u0065\\\\u006C\\\\u006C\\\\u006F\\\\u002C\\\\u0020\\\\u0057\\\\u006F\\\\u0072\\\\u006C\\\\u0064\\\\u0021'\n    );\n  });\n\n  it('should decode an English lowercase hex string correctly', () => {\n    const input =\n      '\\\\u0048\\\\u0065\\\\u006c\\\\u006c\\\\u006f\\\\u002c\\\\u0020\\\\u0057\\\\u006f\\\\u0072\\\\u006c\\\\u0064\\\\u0021';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('Hello, World!');\n  });\n\n  it('should decode an English uppercase hex string correctly', () => {\n    const input =\n      '\\\\u0048\\\\u0065\\\\u006C\\\\u006C\\\\u006F\\\\u002C\\\\u0020\\\\u0057\\\\u006F\\\\u0072\\\\u006C\\\\u0064\\\\u0021';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('Hello, World!');\n  });\n\n  it('should encode a Korean string to lowercase hex correctly', () => {\n    const input = '안녕하세요, 세계!';\n    const result = unicode(input, { mode: 'encode', uppercase: false });\n    expect(result).toBe(\n      '\\\\uc548\\\\ub155\\\\ud558\\\\uc138\\\\uc694\\\\u002c\\\\u0020\\\\uc138\\\\uacc4\\\\u0021'\n    );\n  });\n\n  it('should encode a Korean string to uppercase hex correctly', () => {\n    const input = '안녕하세요, 세계!';\n    const result = unicode(input, { mode: 'encode', uppercase: true });\n    expect(result).toBe(\n      '\\\\uC548\\\\uB155\\\\uD558\\\\uC138\\\\uC694\\\\u002C\\\\u0020\\\\uC138\\\\uACC4\\\\u0021'\n    );\n  });\n\n  it('should decode a Korean lowercase hex string correctly', () => {\n    const input =\n      '\\\\uc548\\\\ub155\\\\ud558\\\\uc138\\\\uc694\\\\u002c\\\\u0020\\\\uc138\\\\uacc4\\\\u0021';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('안녕하세요, 세계!');\n  });\n\n  it('should decode a Korean uppercase hex string correctly', () => {\n    const input =\n      '\\\\uC548\\\\uB155\\\\uD558\\\\uC138\\\\uC694\\\\u002C\\\\u0020\\\\uC138\\\\uACC4\\\\u0021';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('안녕하세요, 세계!');\n  });\n\n  it('should encode a Japanese string to lowercase hex correctly', () => {\n    const input = 'こんにちは、世界！';\n    const result = unicode(input, { mode: 'encode', uppercase: false });\n    expect(result).toBe(\n      '\\\\u3053\\\\u3093\\\\u306b\\\\u3061\\\\u306f\\\\u3001\\\\u4e16\\\\u754c\\\\uff01'\n    );\n  });\n\n  it('should encode a Japanese string to uppercase hex correctly', () => {\n    const input = 'こんにちは、世界！';\n    const result = unicode(input, { mode: 'encode', uppercase: true });\n    expect(result).toBe(\n      '\\\\u3053\\\\u3093\\\\u306B\\\\u3061\\\\u306F\\\\u3001\\\\u4E16\\\\u754C\\\\uFF01'\n    );\n  });\n\n  it('should decode a Japanese lowercase hex string correctly', () => {\n    const input =\n      '\\\\u3053\\\\u3093\\\\u306b\\\\u3061\\\\u306f\\\\u3001\\\\u4e16\\\\u754c\\\\uff01';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('こんにちは、世界！');\n  });\n\n  it('should decode a Japanese uppercase hex string correctly', () => {\n    const input =\n      '\\\\u3053\\\\u3093\\\\u306B\\\\u3061\\\\u306F\\\\u3001\\\\u4E16\\\\u754C\\\\uFF01';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('こんにちは、世界！');\n  });\n\n  it('should encode a Chinese string to lowercase hex correctly', () => {\n    const input = '你好，世界！';\n    const result = unicode(input, { mode: 'encode', uppercase: false });\n    expect(result).toBe('\\\\u4f60\\\\u597d\\\\uff0c\\\\u4e16\\\\u754c\\\\uff01');\n  });\n\n  it('should encode a Chinese string to uppercase hex correctly', () => {\n    const input = '你好，世界！';\n    const result = unicode(input, { mode: 'encode', uppercase: true });\n    expect(result).toBe('\\\\u4F60\\\\u597D\\\\uFF0C\\\\u4E16\\\\u754C\\\\uFF01');\n  });\n\n  it('should decode a Chinese lowercase hex string correctly', () => {\n    const input = '\\\\u4f60\\\\u597d\\\\uff0c\\\\u4e16\\\\u754c\\\\uff01';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('你好，世界！');\n  });\n\n  it('should decode a Chinese uppercase hex string correctly', () => {\n    const input = '\\\\u4F60\\\\u597D\\\\uFF0C\\\\u4E16\\\\u754C\\\\uFF01';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('你好，世界！');\n  });\n\n  it('should encode a Russian string to lowercase hex correctly', () => {\n    const input = 'Привет, мир!';\n    const result = unicode(input, { mode: 'encode', uppercase: false });\n    expect(result).toBe(\n      '\\\\u041f\\\\u0440\\\\u0438\\\\u0432\\\\u0435\\\\u0442\\\\u002c\\\\u0020\\\\u043c\\\\u0438\\\\u0440\\\\u0021'\n    );\n  });\n\n  it('should encode a Russian string to uppercase hex correctly', () => {\n    const input = 'Привет, мир!';\n    const result = unicode(input, { mode: 'encode', uppercase: true });\n    expect(result).toBe(\n      '\\\\u041F\\\\u0440\\\\u0438\\\\u0432\\\\u0435\\\\u0442\\\\u002C\\\\u0020\\\\u043C\\\\u0438\\\\u0440\\\\u0021'\n    );\n  });\n\n  it('should decode a Russian lowercase hex string correctly', () => {\n    const input =\n      '\\\\u041f\\\\u0440\\\\u0438\\\\u0432\\\\u0435\\\\u0442\\\\u002c\\\\u0020\\\\u043c\\\\u0438\\\\u0440\\\\u0021';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('Привет, мир!');\n  });\n\n  it('should decode a Russian uppercase hex string correctly', () => {\n    const input =\n      '\\\\u041F\\\\u0440\\\\u0438\\\\u0432\\\\u0435\\\\u0442\\\\u002C\\\\u0020\\\\u043C\\\\u0438\\\\u0440\\\\u0021';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('Привет, мир!');\n  });\n\n  it('should encode a Spanish string to lowercase hex correctly', () => {\n    const input = '¡Hola, Mundo!';\n    const result = unicode(input, { mode: 'encode', uppercase: false });\n    expect(result).toBe(\n      '\\\\u00a1\\\\u0048\\\\u006f\\\\u006c\\\\u0061\\\\u002c\\\\u0020\\\\u004d\\\\u0075\\\\u006e\\\\u0064\\\\u006f\\\\u0021'\n    );\n  });\n\n  it('should encode a Spanish string to uppercase hex correctly', () => {\n    const input = '¡Hola, Mundo!';\n    const result = unicode(input, { mode: 'encode', uppercase: true });\n    expect(result).toBe(\n      '\\\\u00A1\\\\u0048\\\\u006F\\\\u006C\\\\u0061\\\\u002C\\\\u0020\\\\u004D\\\\u0075\\\\u006E\\\\u0064\\\\u006F\\\\u0021'\n    );\n  });\n\n  it('should decode a Spanish lowercase hex string correctly', () => {\n    const input =\n      '\\\\u00a1\\\\u0048\\\\u006f\\\\u006c\\\\u0061\\\\u002c\\\\u0020\\\\u004d\\\\u0075\\\\u006e\\\\u0064\\\\u006f\\\\u0021';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('¡Hola, Mundo!');\n  });\n\n  it('should decode a Spanish uppercase hex string correctly', () => {\n    const input =\n      '\\\\u00A1\\\\u0048\\\\u006F\\\\u006C\\\\u0061\\\\u002C\\\\u0020\\\\u004D\\\\u0075\\\\u006E\\\\u0064\\\\u006F\\\\u0021';\n    const result = unicode(input, { mode: 'decode', uppercase: false });\n    expect(result).toBe('¡Hola, Mundo!');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/uppercase/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { UppercaseInput } from './service';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolContent from '@components/ToolContent';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {};\n\nconst exampleCards: CardExampleType<typeof initialValues>[] = [\n  {\n    title: 'Convert Text to Uppercase',\n    description: 'This example transforms any text to ALL UPPERCASE format.',\n    sampleText: 'The quick brown fox jumps over the lazy dog.',\n    sampleResult: 'THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.',\n    sampleOptions: {}\n  },\n  {\n    title: 'Uppercase Code',\n    description:\n      'Convert code to uppercase format. Note that this is for display only and would not maintain code functionality.',\n    sampleText: 'function example() { return \"hello world\"; }',\n    sampleResult: 'FUNCTION EXAMPLE() { RETURN \"HELLO WORLD\"; }',\n    sampleOptions: {}\n  },\n  {\n    title: 'Mixed Case to Uppercase',\n    description:\n      'Transform text with mixed casing to consistent all uppercase format.',\n    sampleText: 'ThIs Is MiXeD CaSe TeXt!',\n    sampleResult: 'THIS IS MIXED CASE TEXT!',\n    sampleOptions: {}\n  }\n];\n\nexport default function Uppercase({ title }: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const computeExternal = (\n    _optionsValues: typeof initialValues,\n    input: string\n  ) => {\n    setResult(UppercaseInput(input));\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={null}\n      compute={computeExternal}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('uppercase.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('uppercase.resultTitle')} value={result} />\n      }\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/uppercase/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'uppercase',\n  icon: 'material-symbols-light:format-textdirection-l-to-r',\n\n  keywords: ['uppercase'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:uppercase.title',\n    description: 'string:uppercase.description',\n    shortDescription: 'string:uppercase.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/uppercase/service.ts",
    "content": "export function UppercaseInput(input: string): string {\n  return input.toUpperCase();\n}\n"
  },
  {
    "path": "src/pages/tools/string/uppercase/uppercase.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { UppercaseInput } from './service';\n\ndescribe('UppercaseInput', () => {\n  it('should convert a lowercase string to uppercase', () => {\n    const input = 'hello';\n    const result = UppercaseInput(input);\n    expect(result).toBe('HELLO');\n  });\n\n  it('should convert a mixed case string to uppercase', () => {\n    const input = 'HeLLo WoRLd';\n    const result = UppercaseInput(input);\n    expect(result).toBe('HELLO WORLD');\n  });\n\n  it('should convert an already uppercase string to uppercase', () => {\n    const input = 'HELLO';\n    const result = UppercaseInput(input);\n    expect(result).toBe('HELLO');\n  });\n\n  it('should handle an empty string', () => {\n    const input = '';\n    const result = UppercaseInput(input);\n    expect(result).toBe('');\n  });\n\n  it('should handle a string with numbers and symbols', () => {\n    const input = '123 hello! @world';\n    const result = UppercaseInput(input);\n    expect(result).toBe('123 HELLO! @WORLD');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/string/url-decode/index.tsx",
    "content": "import { useState } from 'react';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { decodeString } from './service';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolContent from '@components/ToolContent';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {};\n\nconst exampleCards: CardExampleType<typeof initialValues>[] = [\n  {\n    title: 'Decode an actual URL',\n    description:\n      'This example decodes a URL-encoded string back to its readable URL form.',\n    sampleText: 'https%3A%2F%2Fomnitools.app%2F',\n    sampleResult: 'https://omnitools.app/',\n    sampleOptions: initialValues\n  },\n  {\n    title: 'Decode All Characters',\n    description:\n      'This example decodes a string where every character has been URL-encoded, restoring the original readable text.',\n    sampleText:\n      '%49%20%63%61%6E%27%74%20%62%65%6C%69%65%76%65%20%69%74%27%73%20%6E%6F%74%20%62%75%74%74%65%72%21',\n    sampleResult: \"I can't believe it's not butter!\",\n    sampleOptions: initialValues\n  }\n];\n\nexport default function DecodeString({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  function compute(_initialValues: typeof initialValues, input: string) {\n    setResult(decodeString(input));\n  }\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={null}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('urlDecode.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('urlDecode.resultTitle')} value={result} />\n      }\n      toolInfo={{\n        title: t('urlDecode.toolInfo.title', { title }),\n        description: longDescription\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/url-decode/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'url-decode-string',\n  icon: 'codicon:symbol-string',\n\n  keywords: ['string', 'unescape', 'encoding', 'percent', 'decoding'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:urlDecode.toolInfo.title',\n    description: 'string:urlDecode.toolInfo.description',\n    shortDescription: 'string:urlDecode.toolInfo.shortDescription',\n    longDescription: 'string:urlDecode.toolInfo.longDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/url-decode/service.ts",
    "content": "export function decodeString(input: string): string {\n  if (!input) return '';\n  return decodeURIComponent(input);\n}\n"
  },
  {
    "path": "src/pages/tools/string/url-encode/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport { useState } from 'react';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { encodeString } from './service';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport { InitialValuesType } from './types';\nimport ToolContent from '@components/ToolContent';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  nonSpecialChar: false\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Encode an actual URL',\n    description:\n      'This example URL-encodes a string that also happens to be a valid web link. Special characters in this example are a colon, slash, question mark and equals sign.',\n    sampleText: 'https://omnitools.app/',\n    sampleResult: 'https%3A%2F%2Fomnitools.app%2F',\n    sampleOptions: initialValues\n  },\n  {\n    title: 'Encode All Characters',\n    description:\n      \"In this example, we've enabled the option that encodes absolutely all characters in a string to URL-encoding. This option makes non-special characters, such as letters get encoded to their hex codes prefixed by a percent sign.\",\n    sampleText: \"I can't believe it's not butter!\",\n    sampleResult:\n      '%49%20%63%61%6E%27%74%20%62%65%6C%69%65%76%65%20%69%74%27%73%20%6E%6F%74%20%62%75%74%74%65%72%21',\n    sampleOptions: {\n      nonSpecialChar: true\n    }\n  }\n];\n\nexport default function EncodeString({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('string');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  function compute(initialValues: InitialValuesType, input: string) {\n    setResult(encodeString(input, initialValues));\n  }\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('urlEncode.encodingOption.title'),\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            checked={values.nonSpecialChar}\n            onChange={(value) => updateField('nonSpecialChar', value)}\n            title={t('urlEncode.encodingOption.nonSpecialCharPlaceholder')}\n            description={t(\n              'urlEncode.encodingOption.nonSpecialCharDescription'\n            )}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={compute}\n      input={input}\n      setInput={setInput}\n      inputComponent={\n        <ToolTextInput\n          title={t('urlEncode.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult title={t('urlEncode.resultTitle')} value={result} />\n      }\n      toolInfo={{\n        title: t('urlEncode.toolInfo.title', { title }),\n        description: longDescription\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/string/url-encode/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('string', {\n  path: 'url-encode-string',\n  icon: 'ic:baseline-percentage',\n\n  keywords: ['string', 'encoding', 'percent', 'escape'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'string:urlEncode.toolInfo.title',\n    description: 'string:urlEncode.toolInfo.description',\n    shortDescription: 'string:urlEncode.toolInfo.shortDescription',\n    longDescription: 'string:urlEncode.toolInfo.longDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/string/url-encode/service.ts",
    "content": "import { InitialValuesType } from './types';\n\nexport function encodeString(\n  input: string,\n  options: InitialValuesType\n): string {\n  if (!input) return '';\n  if (!options.nonSpecialChar) {\n    return encodeURIComponent(input);\n  }\n\n  let result = '';\n  for (const char of input) {\n    const hex = char.codePointAt(0)!.toString(16).toUpperCase();\n    result += '%' + hex.padStart(2, '0');\n  }\n  return result;\n}\n"
  },
  {
    "path": "src/pages/tools/string/url-encode/types.ts",
    "content": "export type InitialValuesType = {\n  nonSpecialChar: boolean;\n};\n"
  },
  {
    "path": "src/pages/tools/time/check-leap-years/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { checkLeapYear } from './service';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {};\n\ntype InitialValuesType = typeof initialValues;\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Find Birthdays on February 29',\n    description:\n      \"One of our friends was born on a leap year on February 29th and as a consequence, she has a birthday only once every 4 years. As we can never really remember when her birthday is, we are using our program to create a reminder list of the upcoming leap years. To create a list of her next birthdays, we load a sequence of years from 2025 to 2040 into the input and get the status of each year. If the program says that it's a leap year, then we know that we'll be invited to a birthday party on February 29th.\",\n    sampleText: `2025\n2026\n2027\n2028\n2029\n2030\n2031\n2032\n2033\n2034\n2035\n2036\n2037\n2038\n2039\n2040`,\n    sampleResult: `2025 is not a leap year.\n2026 is not a leap year.\n2027 is not a leap year.\n2028 is a leap year.\n2029 is not a leap year.\n2030 is not a leap year.\n2031 is not a leap year.\n2032 is a leap year.\n2033 is not a leap year.\n2034 is not a leap year.\n2035 is not a leap year.\n2036 is a leap year.\n2037 is not a leap year.\n2038 is not a leap year.\n2039 is not a leap year.\n2040 is a leap year.`,\n    sampleOptions: {}\n  }\n];\n\nexport default function ConvertDaysToHours({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('time');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: typeof initialValues, input: string) => {\n    setResult(checkLeapYear(input));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = null;\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('checkLeapYears.toolInfo.title', { title }),\n        description: longDescription\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/time/check-leap-years/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('time', {\n  path: 'check-leap-years',\n  icon: 'material-symbols:calendar-month',\n\n  keywords: ['leap', 'year', 'calendar', 'date'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'time:checkLeapYears.title',\n    description: 'time:checkLeapYears.description',\n    shortDescription: 'time:checkLeapYears.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/time/check-leap-years/service.ts",
    "content": "function isLeapYear(year: number): boolean {\n  return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n}\n\nexport function checkLeapYear(input: string): string {\n  if (!input) return '';\n\n  const years = input\n    .split('\\n')\n    .map((year) => year.trim())\n    .filter((year) => year !== '');\n\n  const results = years.map((yearStr) => {\n    if (!/^\\d{1,4}$/.test(yearStr)) {\n      return `${yearStr}: Invalid year`;\n    }\n\n    const year = Number(yearStr);\n    return `${year} ${\n      isLeapYear(year) ? 'is a leap year.' : 'is not a leap year.'\n    }`;\n  });\n\n  return results.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-days-to-hours/convert-days-to-hours.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { convertDaysToHours } from './service';\n\ndescribe('Convert Days to Hours', () => {\n  it('should convert days to hours correctly', () => {\n    expect(convertDaysToHours('2', false)).toBe('48');\n    expect(convertDaysToHours('0', false)).toBe('0');\n  });\n\n  it('should handle invalid input', () => {\n    expect(convertDaysToHours('2\\nc\\n1', false)).toBe('48\\n\\n24');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-days-to-hours/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { convertDaysToHours } from './service';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  hoursFlag: false\n};\ntype InitialValuesType = typeof initialValues;\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Full Days to Hours',\n    description:\n      'This example calculates how many hours there are in 1 day, in one week (7 days), in one month (30 days), and in even longer time periods. To see all the results at once, we enter each individual day value on a new line. We also use the \"days\" suffix in the input and add the \"hours\" suffix to the output.',\n    sampleText: `1 day\n7 days\n30 days\n90 days\n125 days\n500 days`,\n    sampleResult: `24 hours\n168 hours\n720 hours\n2160 hours\n3000 hours\n12000 hours`,\n    sampleOptions: { hoursFlag: true }\n  },\n  {\n    title: 'Fractional Days to Hours',\n    description:\n      'In this example, we convert five decimal fraction day values to hours. Conversion of partial days is similar to the conversion of full days – they are all multiplied by 24. We turn off the option that appends the \"hours\" string after the converted values and get only the numerical hour values in the output.',\n    sampleText: `0.2 d\n1.5 days\n25.25\n9.999\n350.401`,\n    sampleResult: `4.8\n36\n606\n239.976\n8409.624`,\n    sampleOptions: { hoursFlag: false }\n  },\n  {\n    title: 'Number of Hours in a Year',\n    description:\n      'In the modern Gregorian calendar, a common year has 365 days and a leap year has 366 days. This makes the true average length of a year to be 365.242199 days. In this example, we load this number in the input field and convert it to the hours. It turns out that there 8765.812776 hours in an average year.',\n    sampleText: `365.242199 days`,\n    sampleResult: `8765.812776 hours`,\n    sampleOptions: { hoursFlag: true }\n  }\n];\n\nexport default function ConvertDaysToHours({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('time');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: typeof initialValues, input: string) => {\n    setResult(convertDaysToHours(input, optionsValues.hoursFlag));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('convertDaysToHours.hoursName'),\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            onChange={(val) => updateField('hoursFlag', val)}\n            checked={values.hoursFlag}\n            title={t('convertDaysToHours.addHoursName')}\n            description={t('convertDaysToHours.addHoursNameDescription')}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('convertDaysToHours.toolInfo.title'),\n        description: t('convertDaysToHours.toolInfo.description')\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-days-to-hours/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('time', {\n  path: 'convert-days-to-hours',\n  icon: 'material-symbols:schedule',\n\n  keywords: ['days', 'hours', 'convert', 'time'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'time:convertDaysToHours.title',\n    description: 'time:convertDaysToHours.description',\n    shortDescription: 'time:convertDaysToHours.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-days-to-hours/service.ts",
    "content": "import { containsOnlyDigits } from '@utils/string';\n\nfunction compute(input: string) {\n  if (!containsOnlyDigits(input)) {\n    return '';\n  }\n  const days = parseFloat(input);\n  const hours = (days * 24).toFixed(6);\n  return parseFloat(hours);\n}\n\nexport function convertDaysToHours(input: string, hoursFlag: boolean): string {\n  const result: string[] = [];\n\n  const lines = input.split('\\n');\n\n  lines.forEach((line) => {\n    const parts = line.split(' ');\n    const days = parts[0]; // Extract the number before the space\n    const hours = compute(days);\n    result.push(hoursFlag ? `${hours} hours` : `${hours}`);\n  });\n\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-hours-to-days/convert-hours-to-days.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { convertHoursToDays } from './service';\n\ndescribe('convertHoursToDays', () => {\n  it('should convert hours to days with default accuracy', () => {\n    const input = '48';\n    const result = convertHoursToDays(input, '1', false);\n    expect(result).toBe('2');\n  });\n\n  it('should convert hours to days with specified accuracy', () => {\n    const input = '50';\n    const result = convertHoursToDays(input, '2', false);\n    expect(result).toBe('2.08');\n  });\n\n  it('should append \"days\" postfix when daysFlag is true', () => {\n    const input = '72';\n    const result = convertHoursToDays(input, '1', true);\n    expect(result).toBe('3 days');\n  });\n\n  it('should handle multiple lines of input', () => {\n    const input = '24\\n48\\n72';\n    const result = convertHoursToDays(input, '1', true);\n    expect(result).toBe('1 days\\n2 days\\n3 days');\n  });\n\n  it('should handle invalid input gracefully', () => {\n    const input = 'abc';\n    const result = convertHoursToDays(input, '1', false);\n    expect(result).toBe('');\n  });\n\n  it('should handle empty input', () => {\n    const input = '';\n    const result = convertHoursToDays(input, '1', false);\n    expect(result).toBe('');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-hours-to-days/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { convertHoursToDays } from './service';\n\nconst initialValues = {\n  daysFlag: false,\n  accuracy: '1'\n};\ntype InitialValuesType = typeof initialValues;\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Hours to Integer Days',\n    description:\n      'In this example, we convert ten hour values to ten day values. Each input hour is divisible by 24 without a remainder, so all converted output values are full days. To better communicate the time units, we use the word \"hours\" in the input data and also add the word \"days\" to the output data.',\n    sampleText: `24 hours\n48 hours\n72 hours\n96 hours\n120 hours\n144 hours\n168 hours\n192 hours\n216 hours\n240 hours`,\n    sampleResult: `1 day\n2 days\n3 days\n4 days\n5 days\n6 days\n7 days\n8 days\n9 days\n10 days`,\n    sampleOptions: { daysFlag: true, accuracy: '2' }\n  },\n  {\n    title: 'Decimal Days',\n    description:\n      'In this example, we convert five decimal fraction day values to hours. Conversion of partial days is similar to the conversion of full days – they are all multiplied by 24. We turn off the option that appends the \"hours\" string after the converted values and get only the numerical hour values in the output.',\n    sampleText: `1 hr\n100 hr\n9999 hr\n12345 hr\n333333 hr`,\n    sampleResult: `0.0417 days\n4.1667 days\n416.625 days\n514.375 days\n13888.875 days`,\n    sampleOptions: { daysFlag: true, accuracy: '4' }\n  },\n  {\n    title: 'Partial Hours',\n    description:\n      'In the modern Gregorian calendar, a common year has 365 days and a leap year has 366 days. This makes the true average length of a year to be 365.242199 days. In this example, we load this number in the input field and convert it to the hours. It turns out that there 8765.812776 hours in an average year.',\n    sampleText: `0.5\n0.01\n0.99`,\n    sampleResult: `0.02083333\n0.00041667\n0.04125`,\n    sampleOptions: { daysFlag: false, accuracy: '8' }\n  }\n];\n\nexport default function ConvertDaysToHours({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: typeof initialValues, input: string) => {\n    setResult(\n      convertHoursToDays(input, optionsValues.accuracy, optionsValues.daysFlag)\n    );\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Day Value Accuracy',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            description={\n              'If the calculated days is a decimal number, then how many digits should be left after the decimal point?.'\n            }\n            value={values.accuracy}\n            onOwnChange={(val) => updateField('accuracy', val)}\n            type={'text'}\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Days Postfix',\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            onChange={(val) => updateField('daysFlag', val)}\n            checked={values.daysFlag}\n            title={'Append Days Postfix'}\n            description={'Display numeric day values with the postfix \"days\".'}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-hours-to-days/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('time', {\n  path: 'convert-hours-to-days',\n  icon: 'material-symbols:schedule',\n\n  keywords: ['hours', 'days', 'convert', 'time'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'time:convertHoursToDays.title',\n    description: 'time:convertHoursToDays.description',\n    shortDescription: 'time:convertHoursToDays.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-hours-to-days/service.ts",
    "content": "import { containsOnlyDigits } from '@utils/string';\n\nfunction compute(input: string, accuracy: number) {\n  if (!containsOnlyDigits(input)) {\n    return '';\n  }\n  const hours = parseFloat(input);\n  const days = (hours / 24).toFixed(accuracy);\n  return parseFloat(days);\n}\n\nexport function convertHoursToDays(\n  input: string,\n  accuracy: string,\n  daysFlag: boolean\n): string {\n  if (!containsOnlyDigits(accuracy)) {\n    throw new Error('Accuracy contains non digits.');\n  }\n\n  const result: string[] = [];\n\n  const lines = input.split('\\n');\n\n  lines.forEach((line) => {\n    const parts = line.split(' ');\n    const hours = parts[0]; // Extract the number before the space\n    const days = compute(hours, Number(accuracy));\n    result.push(daysFlag ? `${days} days` : `${days}`);\n  });\n\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-seconds-to-time/convert-seconds-to-time.service.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { convertSecondsToTime } from './service';\n\ndescribe('convertSecondsToTime', () => {\n  it('should convert seconds to a formatted time string', () => {\n    const result = convertSecondsToTime('3661', true);\n    expect(result).toBe('01:01:01');\n  });\n\n  it('should handle zero seconds', () => {\n    const result = convertSecondsToTime('0', true);\n    expect(result).toBe('00:00:00');\n  });\n\n  it('should handle seconds less than a minute', () => {\n    const result = convertSecondsToTime('45', true);\n    expect(result).toBe('00:00:45');\n  });\n\n  it('should handle seconds equal to a full minute', () => {\n    const result = convertSecondsToTime('60', true);\n    expect(result).toBe('00:01:00');\n  });\n\n  it('should handle seconds equal to a full hour', () => {\n    const result = convertSecondsToTime('3600', true);\n    expect(result).toBe('01:00:00');\n  });\n  it('should handle seconds equal to a full hour without padding', () => {\n    const result = convertSecondsToTime('3600', false);\n    expect(result).toBe('1:0:0');\n  });\n\n  it('should handle large numbers of seconds with padding', () => {\n    const result = convertSecondsToTime('7325', true);\n    expect(result).toBe('02:02:05');\n  });\n  it('should handle large numbers of seconds without padding', () => {\n    const result = convertSecondsToTime('7325', false);\n    expect(result).toBe('2:2:5');\n  });\n  it('should handle numbers of seconds on multilines without padding', () => {\n    const result = convertSecondsToTime('7325\\n3600\\n5c\\n60', false);\n    expect(result).toBe('2:2:5\\n1:0:0\\n\\n0:1:0');\n  });\n  it('should handle numbers of seconds on multilines with padding', () => {\n    const result = convertSecondsToTime('7325\\n3600\\n5c\\n60', true);\n    expect(result).toBe('02:02:05\\n01:00:00\\n\\n00:01:00');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-seconds-to-time/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { convertSecondsToTime } from './service';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  paddingFlag: false\n};\ntype InitialValuesType = typeof initialValues;\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: '1 Second, 1 Minute, 1 Hour',\n    description:\n      \"In this example, we convert 1 second, 60 seconds, and 3600 seconds to clock format. We don't use the zero-padding option and get three simple output values – 0:0:1 for 1 second, 0:1:0 for 60 seconds (1 minute), and 1:0:0 for 3600 seconds (1 hour).\",\n    sampleText: `1\n60\n3600`,\n    sampleResult: `0:0:1\n0:1:0\n1:0:0`,\n    sampleOptions: { paddingFlag: false }\n  },\n  {\n    title: 'HH:MM:SS Digital Clock',\n    description:\n      \"In this example, we enable the padding option and output digital clock time in the format HH:MM:SS. The first two integer timestamps don't contain a full minute and the third timestamp doesn't contain a full hour, there we get zeros in the minutes or hours positions in output.\",\n    sampleText: `0\n46\n890\n18305\n40271\n86399`,\n    sampleResult: `00:00:00\n00:00:46\n00:14:50\n05:05:05\n11:11:11\n23:59:59`,\n    sampleOptions: { paddingFlag: true }\n  },\n  {\n    title: 'More Than a Day',\n    description:\n      \"The values of all input seconds in this example are greater than the number of seconds in a day (86400 seconds). As our algorithm doesn't limit the time to just 23:59:59 hours, it can find the exact number of hours in large inputs.\",\n    sampleText: `86401\n123456\n2159999\n\n3600000\n101010101`,\n    sampleResult: `24:00:01\n34:17:36\n599:59:59\n\n1000:00:00\n28058:21:41`,\n    sampleOptions: { paddingFlag: true }\n  }\n];\n\nexport default function SecondsToTime({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('time');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: typeof initialValues, input: string) => {\n    setResult(convertSecondsToTime(input, optionsValues.paddingFlag));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('convertSecondsToTime.timePadding'),\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            onChange={(val) => updateField('paddingFlag', val)}\n            checked={values.paddingFlag}\n            title={t('convertSecondsToTime.addPadding')}\n            description={t('convertSecondsToTime.addPaddingDescription')}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('convertSecondsToTime.toolInfo.title', { title }),\n        description: longDescription\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-seconds-to-time/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('time', {\n  path: 'convert-seconds-to-time',\n  icon: 'material-symbols:schedule',\n\n  keywords: ['seconds', 'time', 'convert', 'format'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'time:convertSecondsToTime.title',\n    description: 'time:convertSecondsToTime.description',\n    shortDescription: 'time:convertSecondsToTime.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-seconds-to-time/service.ts",
    "content": "import { containsOnlyDigits } from '@utils/string';\n\nfunction compute(seconds: string, paddingFlag: boolean): string {\n  if (!containsOnlyDigits(seconds)) {\n    return '';\n  }\n  const hours = Math.floor(Number(seconds) / 3600);\n  const minutes = Math.floor((Number(seconds) % 3600) / 60);\n  const secs = Number(seconds) % 60;\n  return paddingFlag\n    ? [hours, minutes, secs]\n        .map((unit) => String(unit).padStart(2, '0')) // Ensures two-digit format\n        .join(':')\n    : [hours, minutes, secs].join(':');\n}\n\nexport function convertSecondsToTime(\n  input: string,\n  paddingFlag: boolean\n): string {\n  const result: string[] = [];\n\n  const lines = input.split('\\n');\n\n  lines.forEach((line) => {\n    const seconds = line.trim();\n    const time = compute(seconds, paddingFlag);\n    result.push(time);\n  });\n\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-time-to-decimal/convert-time-to-decimal.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { convertTimeToDecimal } from './service';\n\ndescribe('convert-time-to-decimal', () => {\n  it('should convert time to decimal with default decimal places', () => {\n    const input = '31:23:59';\n    const result = convertTimeToDecimal(input, { decimalPlaces: '6' });\n    expect(result).toBe('31.399722');\n  });\n\n  it('should convert time to decimal with specified decimal places', () => {\n    const input = '31:23:59';\n    const result = convertTimeToDecimal(input, { decimalPlaces: '10' });\n    expect(result).toBe('31.3997222222');\n  });\n\n  it('should convert time to decimal with supplied format of HH:MM:SS', () => {\n    const input = '13:25:30';\n    const result = convertTimeToDecimal(input, { decimalPlaces: '6' });\n    expect(result).toBe('13.425000');\n  });\n\n  it('should convert time to decimal with supplied format of HH:MM', () => {\n    const input = '13:25';\n    const result = convertTimeToDecimal(input, { decimalPlaces: '6' });\n    expect(result).toBe('13.416667');\n  });\n\n  it('should convert time to decimal with supplied format of HH:MM:', () => {\n    const input = '13:25';\n    const result = convertTimeToDecimal(input, { decimalPlaces: '6' });\n    expect(result).toBe('13.416667');\n  });\n\n  it('should convert time to decimal with supplied format of HH.MM.SS', () => {\n    const input = '13.25.30';\n    const result = convertTimeToDecimal(input, { decimalPlaces: '6' });\n    expect(result).toBe('13.425000');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-time-to-decimal/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { convertTimeToDecimal } from './service';\nimport { InitialValuesType } from './types';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\n\nconst initialValues: InitialValuesType = {\n  decimalPlaces: '6'\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Convert time to decimal',\n    description:\n      'This example shows how to convert a formatted time (HH:MM:SS) to a decimal version.',\n    sampleText: '31:23:59',\n    sampleResult: `31.399722`,\n    sampleOptions: {\n      decimalPlaces: '6'\n    }\n  }\n];\nexport default function ConvertTimeToDecimal({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: InitialValuesType, input: string) => {\n    setResult(convertTimeToDecimal(input, values));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Decimal places',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            description={'How many decimal places should the result contain?'}\n            value={values.decimalPlaces}\n            onOwnChange={(val) => updateField('decimalPlaces', val)}\n            type={'text'}\n          />\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      exampleCards={exampleCards}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-time-to-decimal/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('time', {\n  i18n: {\n    name: 'time:convertTimeToDecimal.title',\n    description: 'time:convertTimeToDecimal.description',\n    shortDescription: 'time:convertTimeToDecimal.shortDescription',\n    longDescription: 'time:convertTimeToDecimal.longDescription'\n  },\n  path: 'convert-time-to-decimal',\n  icon: 'material-symbols-light:decimal-increase-rounded',\n  keywords: ['convert', 'time', 'to', 'decimal'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-time-to-decimal/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { humanTimeValidation } from 'utils/time';\n\nexport function convertTimeToDecimal(\n  input: string,\n  options: InitialValuesType\n): string {\n  if (!input) return '';\n\n  const dp = parseInt(options.decimalPlaces, 10);\n  if (isNaN(dp) || dp < 0) {\n    return 'Invalid decimal places value.';\n  }\n\n  // Multiple lines processing\n  const lines = input.split('\\n');\n  if (!lines) return '';\n\n  const result: string[] = [];\n\n  lines.forEach((line) => {\n    line = line.trim();\n    if (!line) return;\n\n    const { isValid, hours, minutes, seconds } = humanTimeValidation(line);\n\n    if (!isValid) {\n      result.push('Incorrect input format use `HH:MM:(SS)` or `HH.MM.(SS )`.');\n      return;\n    }\n\n    const decimalTime = hours + minutes / 60 + seconds / 3600;\n    result.push(decimalTime.toFixed(dp).toString());\n  });\n\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-time-to-decimal/types.ts",
    "content": "export type InitialValuesType = {\n  decimalPlaces: string;\n};\n"
  },
  {
    "path": "src/pages/tools/time/convert-time-to-seconds/convert-time-to-seconds.service.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { convertTimetoSeconds } from './service';\n\ndescribe('convertSecondsToTime', () => {\n  it('should convert valid time strings to seconds', () => {\n    const input = '01:02:03\\n00:45:30\\n12:00';\n    const expectedOutput = '3723\\n2730\\n43200';\n    expect(convertTimetoSeconds(input)).toBe(expectedOutput);\n  });\n\n  it('should handle single-line input', () => {\n    const input = '00:01:30';\n    const expectedOutput = '90';\n    expect(convertTimetoSeconds(input)).toBe(expectedOutput);\n  });\n\n  it('should throw an error for invalid time format', () => {\n    const input = '01:02:03\\n01:02:03:04';\n    expect(() => convertTimetoSeconds(input)).toThrow(\n      'Time contains more than 3 parts on line 2'\n    );\n  });\n\n  it('should throw an error for non-numeric values (minutes)', () => {\n    const input = '01:XX:03';\n    expect(() => convertTimetoSeconds(input)).toThrow(\n      \"Time doesn't contain valid minutes on line 1\"\n    );\n  });\n\n  it('should throw an error for non-numeric values (hours)', () => {\n    const input = '0x:00:03';\n    expect(() => convertTimetoSeconds(input)).toThrow(\n      \"Time doesn't contain valid hours on line 1\"\n    );\n  });\n\n  it('should throw an error for non-numeric values (seconds)', () => {\n    const input = '01:00:0s';\n    expect(() => convertTimetoSeconds(input)).toThrow(\n      \"Time doesn't contain valid seconds on line 1\"\n    );\n  });\n\n  it('should throw an error for non-numeric values multi lines (seconds)', () => {\n    const input = '01:00:00\\n01:00:0s';\n    expect(() => convertTimetoSeconds(input)).toThrow(\n      \"Time doesn't contain valid seconds on line 2\"\n    );\n  });\n\n  it('should handle empty input', () => {\n    const input = '';\n    const expectedOutput = '';\n    expect(convertTimetoSeconds(input)).toBe(expectedOutput);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-time-to-seconds/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { convertTimetoSeconds } from './service';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {};\ntype InitialValuesType = typeof initialValues;\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Multiple Clock Times',\n    description:\n      'In this example, we convert multiple clock times to seconds. Each clock time is listed on a new line and the spacing between input times is preserved in the output.',\n    sampleText: `00:00:00\n\n00:00:01\n00:01:00\n01:00:00\n01:59:59\n12:00:00\n18:30:30\n23:59:59\n\n24:00:00`,\n    sampleResult: `0\n\n1\n60\n3600\n7199\n43200\n66630\n86399\n\n86400`,\n    sampleOptions: {}\n  },\n  {\n    title: 'Partial Clock Times',\n    description:\n      'This example finds how many seconds there are in clock times that are partially written. Some of the clock times contain just the hours and some others contain just hours and minutes.',\n    sampleText: `1\n1:10\n14:44\n23`,\n    sampleResult: `3600\n4200\n53040\n82800`,\n    sampleOptions: {}\n  },\n  {\n    title: 'Time Beyond 24 Hours',\n    description:\n      'In this example, we go beyond the regular 24-hour clock. In fact, we even go beyond 60 minutes and 60 seconds.',\n    sampleText: `24:00:01\n48:00:00\n72\n\n00:100:00\n100:100:100`,\n    sampleResult: `86401\n172800\n259200\n\n6000\n366100`,\n    sampleOptions: {}\n  }\n];\n\nexport default function TimeToSeconds({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('time');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: typeof initialValues, input: string) => {\n    setResult(convertTimetoSeconds(input));\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      getGroups={null}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('convertTimeToSeconds.toolInfo.title', { title }),\n        description: longDescription\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-time-to-seconds/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('time', {\n  path: 'convert-time-to-seconds',\n  icon: 'material-symbols:schedule',\n\n  keywords: ['time', 'seconds', 'convert', 'format', 'HH:MM:SS'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'time:convertTimeToSeconds.title',\n    description: 'time:convertTimeToSeconds.description',\n    shortDescription: 'time:convertTimeToSeconds.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-time-to-seconds/service.ts",
    "content": "import { containsOnlyDigits } from '@utils/string';\n\nfunction recursiveTimeToSeconds(\n  timeArray: string[],\n  index: number = 0\n): number {\n  if (index >= timeArray.length) {\n    return 0;\n  }\n\n  const multipliers = [3600, 60, 1];\n  return (\n    Number(timeArray[index]) * multipliers[index] +\n    recursiveTimeToSeconds(timeArray, index + 1)\n  );\n}\n\nfunction compute(timeArray: string[], lineNumber: number): string {\n  if (timeArray[0] == '') {\n    return '';\n  }\n  if (timeArray.length > 3) {\n    throw new Error(`Time contains more than 3 parts on line ${lineNumber}`);\n  }\n\n  const normalizedArray = [...timeArray, '0', '0'];\n\n  normalizedArray.forEach((time, index) => {\n    if (!containsOnlyDigits(time)) {\n      throw new Error(\n        `Time doesn't contain valid ${\n          ['hours', 'minutes', 'seconds'][index]\n        } on line ${lineNumber}`\n      );\n    }\n  });\n\n  return recursiveTimeToSeconds(timeArray).toString();\n}\n\nexport function convertTimetoSeconds(input: string): string {\n  const result: string[] = [];\n\n  const lines = input.split('\\n');\n\n  lines.forEach((line, index) => {\n    const timeArray = line.split(':');\n    const seconds = compute(timeArray, index + 1);\n    result.push(seconds);\n  });\n\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-unix-to-date/convert-unix-to-date.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { convertUnixToDate } from './service';\n\ndescribe('convertUnixToDate', () => {\n  it('should convert a single Unix timestamp with label (UTC)', () => {\n    const input = '0';\n    const result = convertUnixToDate(input, true, false);\n    expect(result).toBe('1970-01-01 00:00:00.000 UTC');\n  });\n\n  it('should convert a single Unix timestamp without label (UTC)', () => {\n    const input = '1234567890';\n    const result = convertUnixToDate(input, false, false);\n    expect(result).toBe('2009-02-13 23:31:30.000');\n  });\n\n  it('should convert a single Unix timestamp in local time', () => {\n    const input = '1234567890';\n    const result = convertUnixToDate(input, true, true);\n    expect(result.endsWith('UTC')).toBe(false);\n  });\n\n  it('should handle multiple lines with label (UTC)', () => {\n    const input = '0\\n2147483647';\n    const result = convertUnixToDate(input, true, false);\n    expect(result).toBe(\n      '1970-01-01 00:00:00.000 UTC\\n2038-01-19 03:14:07.000 UTC'\n    );\n  });\n\n  it('should handle multiple lines with local time', () => {\n    const input = '1672531199\\n1721287227';\n    const result = convertUnixToDate(input, false, true);\n    const lines = result.split('\\n');\n    expect(lines.length).toBe(2);\n    expect(lines[0].endsWith('UTC')).toBe(false);\n  });\n\n  it('should return empty string for invalid input', () => {\n    const input = 'not_a_number';\n    const result = convertUnixToDate(input, true, false);\n    expect(result).toBe('');\n  });\n\n  it('should return empty string for empty input', () => {\n    const input = '';\n    const result = convertUnixToDate(input, false, false);\n    expect(result).toBe('');\n  });\n\n  it('should ignore invalid lines in multiline input', () => {\n    const input = 'abc\\n1600000000';\n    const result = convertUnixToDate(input, true, false);\n    expect(result).toBe('\\n2020-09-13 12:26:40.000 UTC');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-unix-to-date/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { convertUnixToDate } from './service';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  withLabel: true,\n  useLocalTime: false\n};\ntype InitialValuesType = typeof initialValues;\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Basic Unix Time to Date',\n    description:\n      'This example shows how Unix timestamps are converted into human-readable dates. Each timestamp represents the number of seconds that have elapsed since January 1, 1970 (UTC).',\n    sampleText: `0\n1721287227\n2147483647`,\n    sampleResult: `1970-01-01 00:00:00:000 UTC\n2024-07-18 10:00:27:000 UTC\n2038-01-19 03:14:07:000 UTC`,\n    sampleOptions: { withLabel: true, useLocalTime: false }\n  },\n  {\n    title: 'Without UTC Suffix',\n    description:\n      'In this example, the UTC suffix is removed from the output. This might be useful for embedding timestamps into other formats or for cleaner display.',\n    sampleText: `1234567890\n1672531199`,\n    sampleResult: `2009-02-13 23:31:30\n2022-12-31 23:59:59:000`,\n    sampleOptions: { withLabel: false, useLocalTime: false }\n  },\n  {\n    title: 'Use Local Time',\n    description:\n      'This example demonstrates how timestamps are shown in your local timezone rather than UTC. The UTC suffix is omitted in this case.',\n    sampleText: `1721287227`,\n    sampleResult: `2024-07-18 12:00:27`,\n    sampleOptions: { withLabel: true, useLocalTime: true }\n  }\n];\n\nexport default function ConvertUnixToDate({ title }: ToolComponentProps) {\n  const { t } = useTranslation('time');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (values: typeof initialValues, input: string) => {\n    setResult(convertUnixToDate(input, values.withLabel, values.useLocalTime));\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('convertUnixToDate.withLabel'),\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            onChange={(val) => updateField('withLabel', val)}\n            checked={values.withLabel}\n            title={t('convertUnixToDate.addUtcLabel')}\n            description={t('convertUnixToDate.addUtcLabelDescription')}\n          />\n          <CheckboxWithDesc\n            onChange={(val) => updateField('useLocalTime', val)}\n            checked={values.useLocalTime}\n            title={t('convertUnixToDate.useLocalTime')}\n            description={t('convertUnixToDate.useLocalTimeDescription')}\n          />\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      exampleCards={exampleCards}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('convertUnixToDate.toolInfo.title'),\n        description: t('convertUnixToDate.toolInfo.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/time/convert-unix-to-date/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('time', {\n  i18n: {\n    name: 'time:convertUnixToDate.title',\n    description: 'time:convertUnixToDate.description',\n    shortDescription: 'time:convertUnixToDate.shortDescription'\n  },\n  path: 'convert-unix-to-date',\n  icon: 'material-symbols:schedule',\n  keywords: ['convert', 'unix', 'to', 'date'],\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/time/convert-unix-to-date/service.ts",
    "content": "import { containsOnlyDigits } from '@utils/string';\n\nfunction computeUnixToDate(input: string, useLocalTime: boolean): string {\n  if (!containsOnlyDigits(input)) {\n    return '';\n  }\n  const timestamp = parseInt(input, 10);\n  const date = new Date(timestamp * 1000); // Convert from seconds to milliseconds\n\n  if (useLocalTime) {\n    const year = date.getFullYear();\n    const month = String(date.getMonth() + 1).padStart(2, '0');\n    const day = String(date.getDate()).padStart(2, '0');\n    const hours = String(date.getHours()).padStart(2, '0');\n    const minutes = String(date.getMinutes()).padStart(2, '0');\n    const seconds = String(date.getSeconds()).padStart(2, '0');\n    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;\n  } else {\n    return date.toISOString().replace('T', ' ').replace('Z', '');\n  }\n}\n\nexport function convertUnixToDate(\n  input: string,\n  withLabel: boolean,\n  useLocalTime: boolean\n): string {\n  const result: string[] = [];\n\n  const lines = input.split('\\n');\n\n  lines.forEach((line) => {\n    const parts = line.split(' ');\n    const timestamp = parts[0];\n    const formattedDate = computeUnixToDate(timestamp, useLocalTime);\n\n    const label = !useLocalTime && withLabel ? ' UTC' : '';\n    result.push(formattedDate ? `${formattedDate}${label}` : '');\n  });\n\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/time/crontab-guru/crontab-guru.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { validateCrontab, explainCrontab } from './service';\n\ndescribe('crontab-guru service', () => {\n  it('validates correct crontab expressions', () => {\n    expect(validateCrontab('35 16 * * 0-5')).toBe(true);\n    expect(validateCrontab('* * * * *')).toBe(true);\n    expect(validateCrontab('0 12 1 * *')).toBe(true);\n  });\n\n  it('invalidates incorrect crontab expressions', () => {\n    expect(validateCrontab('invalid expression')).toBe(false);\n    expect(validateCrontab('61 24 * * *')).toBe(false);\n  });\n\n  it('explains valid crontab expressions', () => {\n    expect(explainCrontab('35 16 * * 0-5')).toMatch(/At 04:35 PM/);\n    expect(explainCrontab('* * * * *')).toMatch(/Every minute/);\n  });\n\n  it('returns error for invalid crontab explanation', () => {\n    expect(explainCrontab('invalid expression')).toMatch(\n      /Invalid crontab expression/\n    );\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/time/crontab-guru/index.tsx",
    "content": "import { Alert } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { main, validateCrontab } from './service';\n\nconst initialValues = {};\n\ntype InitialValuesType = typeof initialValues;\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Every day at 16:35, Sunday to Friday',\n    description: 'At 16:35 on every day-of-week from Sunday through Friday.',\n    sampleText: '35 16 * * 0-5',\n    sampleResult: 'At 04:35 PM, Sunday through Friday',\n    sampleOptions: {}\n  },\n  {\n    title: 'Every minute',\n    description: 'Runs every minute.',\n    sampleText: '* * * * *',\n    sampleResult: 'Every minute',\n    sampleOptions: {}\n  },\n  {\n    title: 'Every 5 minutes',\n    description: 'Runs every 5 minutes.',\n    sampleText: '*/5 * * * *',\n    sampleResult: 'Every 5 minutes',\n    sampleOptions: {}\n  },\n  {\n    title: 'At 12:00 PM on the 1st of every month',\n    description: 'Runs at noon on the first day of each month.',\n    sampleText: '0 12 1 * *',\n    sampleResult: 'At 12:00 PM, on day 1 of the month',\n    sampleOptions: {}\n  }\n];\n\nexport default function CrontabGuru({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n  const [isValid, setIsValid] = useState<boolean | null>(null);\n  const [hasInteracted, setHasInteracted] = useState<boolean>(false);\n\n  const compute = (values: InitialValuesType, input: string) => {\n    if (hasInteracted) {\n      setIsValid(validateCrontab(input));\n    }\n    setResult(main(input, values));\n  };\n\n  const handleInputChange = (val: string) => {\n    if (!hasInteracted) setHasInteracted(true);\n    setInput(val);\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput\n          value={input}\n          onChange={handleInputChange}\n          placeholder=\"e.g. 35 16 * * 0-5\"\n        />\n      }\n      resultComponent={\n        <div style={{ position: 'relative', minHeight: 80 }}>\n          {hasInteracted && isValid === false && (\n            <div\n              style={{\n                position: 'absolute',\n                top: 0,\n                left: 0,\n                width: '100%',\n                height: '100%',\n                zIndex: 2,\n                pointerEvents: 'auto',\n                display: 'flex',\n                alignItems: 'center',\n                justifyContent: 'center',\n                background: 'transparent'\n              }}\n            >\n              <Alert\n                severity=\"error\"\n                style={{\n                  width: '80%',\n                  opacity: 0.85,\n                  textAlign: 'center',\n                  pointerEvents: 'none'\n                }}\n              >\n                Invalid crontab expression.\n              </Alert>\n            </div>\n          )}\n          <div\n            style={{\n              filter: hasInteracted && isValid === false ? 'blur(1px)' : 'none',\n              transition: 'filter 0.2s'\n            }}\n          >\n            <ToolTextResult\n              value={hasInteracted && isValid === false ? '' : result}\n            />\n          </div>\n        </div>\n      }\n      initialValues={initialValues}\n      exampleCards={exampleCards}\n      getGroups={null}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/time/crontab-guru/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('time', {\n  path: 'crontab-guru',\n  icon: 'material-symbols:schedule',\n  keywords: [\n    'crontab',\n    'cron',\n    'schedule',\n    'guru',\n    'time',\n    'expression',\n    'parser',\n    'explain'\n  ],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'time:crontabGuru.title',\n    description: 'time:crontabGuru.description',\n    shortDescription: 'time:crontabGuru.shortDescription',\n    userTypes: ['developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/time/crontab-guru/service.ts",
    "content": "import cronstrue from 'cronstrue';\nimport { isValidCron } from 'cron-validator';\n\nexport function explainCrontab(expr: string): string {\n  try {\n    return cronstrue.toString(expr);\n  } catch (e: any) {\n    return `Invalid crontab expression: ${e.message}`;\n  }\n}\n\nexport function validateCrontab(expr: string): boolean {\n  return isValidCron(expr, { seconds: false, allowBlankDay: true });\n}\n\nexport function main(input: string, _options: any): string {\n  if (!input.trim()) return '';\n  if (!validateCrontab(input)) {\n    return 'Invalid crontab expression.';\n  }\n  return explainCrontab(input);\n}\n"
  },
  {
    "path": "src/pages/tools/time/discord-timestamp/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport { DiscordTimestampGenerator } from './service';\nimport { useTranslation } from 'react-i18next';\nimport { DiscordTimestampFormat, InitialValuesType } from './types';\n\nconst initialValues: InitialValuesType = {\n  format: 'F',\n  enforceUTC: true\n};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Convert a single datetime',\n    description:\n      \"In this example, we convert a single datetime into a Discord timestamp using the Long Date & Time format (F). The input is an ISO 8601 datetime string and the output is a Discord timestamp that will display the full date and time in each viewer's local timezone.\",\n    sampleText: `2025-03-15T10:00:00`,\n    sampleResult: `<t:1742032800:F>`,\n    sampleOptions: {\n      ...initialValues,\n      format: 'F'\n    }\n  },\n  {\n    title: 'Convert multiple datetimes',\n    description:\n      'In this example, we convert multiple datetimes at once — one per line. Blank lines are preserved in the output. Each valid datetime is converted to a Discord timestamp using the Short Date & Time format (f).',\n    sampleText: `2025-03-15T10:00:00\n2025-07-20T18:45:00\n\n2025-11-05T08:15:00`,\n    sampleResult: `<t:1742032800:f>\n<t:1753037100:f>\n\n<t:1762330500:f>`,\n    sampleOptions: {\n      ...initialValues,\n      format: 'f'\n    }\n  },\n  {\n    title: 'Relative timestamps',\n    description:\n      'In this example, we use the Relative format (R) which displays how long ago or how far in the future a datetime is — for example \"2 hours ago\" or \"in 3 days\". This format is useful for countdowns or activity feeds and updates dynamically in Discord.',\n    sampleText: `2025-03-15T10:00:00\n2025-07-20T18:45:00\n2025-11-05T08:15:00`,\n    sampleResult: `<t:1742032800:R>\n<t:1753037100:R>\n<t:1762330500:R>`,\n    sampleOptions: {\n      ...initialValues,\n      format: 'R'\n    }\n  }\n];\n\ntype formatProps = {\n  label: string;\n  value: DiscordTimestampFormat;\n};\n\nexport default function TruncateClockTime({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('time');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: InitialValuesType, input: string) => {\n    setResult(DiscordTimestampGenerator(input, optionsValues));\n  };\n\n  const FORMATS: formatProps[] = [\n    { label: t('discordTimestamp.formats.short_time'), value: 't' },\n    { label: t('discordTimestamp.formats.long_time'), value: 'T' },\n    { label: t('discordTimestamp.formats.short_date'), value: 'd' },\n    { label: t('discordTimestamp.formats.long_date'), value: 'D' },\n    { label: t('discordTimestamp.formats.short_datetime'), value: 'f' },\n    { label: t('discordTimestamp.formats.long_datetime'), value: 'F' },\n    { label: t('discordTimestamp.formats.relative'), value: 'R' }\n  ];\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('discordTimestamp.utc.title'),\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            checked={values.enforceUTC}\n            onChange={(value) => updateField('enforceUTC', value)}\n            title={t('discordTimestamp.utc.label')}\n            description={t('discordTimestamp.utc.description')}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('discordTimestamp.formats.title'),\n      component: (\n        <Box>\n          <SelectWithDesc\n            selected={values.format}\n            onChange={(value) => updateField('format', value)}\n            options={FORMATS.map((format) => ({\n              label: format.label,\n              value: format.value\n            }))}\n            description={t('discordTimestamp.formats.description')}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput\n          value={input}\n          title={t('discordTimestamp.inputTitle')}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult\n          title={t('discordTimestamp.outputTitle')}\n          value={result}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: title,\n        description: longDescription\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/time/discord-timestamp/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\nconst DiscordTimestamp = lazy(() => import('./index'));\n\nexport const tool = defineTool('time', {\n  path: 'discord-timestamp',\n  component: DiscordTimestamp,\n  icon: 'ic:baseline-discord',\n  keywords: ['discord', 'timestamp', 'time', 'datetime'],\n  i18n: {\n    name: 'time:discordTimestamp.name',\n    description: 'time:discordTimestamp.description',\n    shortDescription: 'time:discordTimestamp.shortDescription',\n    longDescription: 'time:discordTimestamp.toolInfo.description',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/time/discord-timestamp/service.ts",
    "content": "import { DiscordTimestampFormat, InitialValuesType } from './types';\n\n/**\n * Converts a Date object into a Discord timestamp string.\n *\n * @param date   JavaScript Date object to convert.\n * @param format Discord timestamp format.\n *               Allowed values: 't', 'T', 'd', 'D', 'f', 'F', 'R'.\n *               Defaults to 'F' (full date and time).\n *\n * @returns Discord-formatted timestamp string.\n */\nexport function compute(\n  date: Date,\n  format: DiscordTimestampFormat = 'F'\n): string {\n  const unix = Math.floor(date.getTime() / 1000);\n  return `<t:${unix}:${format}>`;\n}\n\n/**\n * Converts a multiline string of datetime values into Discord timestamp strings.\n *\n * Processes each line independently:\n * - Blank lines are preserved as-is in the output.\n * - Valid datetime strings are converted to Discord timestamp format.\n * - Invalid or unparseable lines show error message.\n *\n * @param input   Multiline string where each line is an UTC datetime value.\n * @param options - `format`: Discord timestamp format ('t', 'T', 'd', 'D', 'f', 'F', 'R').\n *                - `enforceUTC`: if true, input is treated as UTC; otherwise as local time.\n *\n * @returns Multiline string with each valid datetime replaced by a Discord timestamp.\n */\nexport function DiscordTimestampGenerator(\n  input: string,\n  options: InitialValuesType\n): string {\n  if (!input) return '';\n\n  const { format, enforceUTC } = options;\n\n  const results: string[] = [];\n\n  input.split('\\n').forEach((datetime) => {\n    if (!datetime.trim()) {\n      results.push(''); // Blank lines stays\n      return;\n    }\n\n    const raw = datetime.trim();\n    const date = enforceUTC ? new Date(raw + 'Z') : new Date(raw);\n    if (!isNaN(date.getTime())) {\n      results.push(compute(date, format));\n    } else {\n      results.push(`❌  ${datetime}`); // keep original if unparseable\n    }\n  });\n\n  return results.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/time/discord-timestamp/types.ts",
    "content": "export type DiscordTimestampFormat = 't' | 'T' | 'd' | 'D' | 'f' | 'F' | 'R';\n\nexport type InitialValuesType = {\n  format: DiscordTimestampFormat;\n  enforceUTC: boolean;\n};\n"
  },
  {
    "path": "src/pages/tools/time/index.ts",
    "content": "import { tool as timeConvertTimeToDecimal } from './convert-time-to-decimal/meta';\nimport { tool as timeConvertUnixToDate } from './convert-unix-to-date/meta';\nimport { tool as timeCrontabGuru } from './crontab-guru/meta';\nimport { tool as timeBetweenDates } from './time-between-dates/meta';\nimport { tool as daysDoHours } from './convert-days-to-hours/meta';\nimport { tool as hoursToDays } from './convert-hours-to-days/meta';\nimport { tool as convertSecondsToTime } from './convert-seconds-to-time/meta';\nimport { tool as convertTimetoSeconds } from './convert-time-to-seconds/meta';\nimport { tool as truncateClockTime } from './truncate-clock-time/meta';\nimport { tool as checkLeapYear } from './check-leap-years/meta';\nimport { tool as discordTimestamp } from './discord-timestamp/meta';\n\nexport const timeTools = [\n  daysDoHours,\n  hoursToDays,\n  convertSecondsToTime,\n  convertTimetoSeconds,\n  truncateClockTime,\n  timeBetweenDates,\n  timeCrontabGuru,\n  checkLeapYear,\n  timeConvertUnixToDate,\n  timeConvertTimeToDecimal,\n  discordTimestamp\n];\n"
  },
  {
    "path": "src/pages/tools/time/time-between-dates/index.tsx",
    "content": "import { Box, Paper, Typography } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport SelectWithDesc from '@components/options/SelectWithDesc';\nimport {\n  calculateTimeBetweenDates,\n  formatTimeWithLargestUnit,\n  getTimeWithTimezone,\n  unitHierarchy\n} from './service';\nimport * as Yup from 'yup';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { useTranslation } from 'react-i18next';\n\ntype TimeUnit =\n  | 'milliseconds'\n  | 'seconds'\n  | 'minutes'\n  | 'hours'\n  | 'days'\n  | 'months'\n  | 'years';\n\ntype InitialValuesType = {\n  startDate: string;\n  startTime: string;\n  endDate: string;\n  endTime: string;\n  startTimezone: string;\n  endTimezone: string;\n};\n\nconst initialValues: InitialValuesType = {\n  startDate: new Date().toISOString().split('T')[0],\n  startTime: '00:00',\n  endDate: new Date().toISOString().split('T')[0],\n  endTime: '12:00',\n  startTimezone: 'local',\n  endTimezone: 'local'\n};\n\nconst validationSchema = Yup.object({\n  startDate: Yup.string().required('Start date is required'),\n  startTime: Yup.string().required('Start time is required'),\n  endDate: Yup.string().required('End date is required'),\n  endTime: Yup.string().required('End time is required'),\n  startTimezone: Yup.string(),\n  endTimezone: Yup.string()\n});\n\nconst timezoneOptions = [\n  { value: 'local', label: 'Local Time' },\n  ...Array.from(\n    new Map(\n      Intl.supportedValuesOf('timeZone').map((tz) => {\n        const formatter = new Intl.DateTimeFormat('en', {\n          timeZone: tz,\n          timeZoneName: 'shortOffset'\n        });\n\n        const offset =\n          formatter\n            .formatToParts(new Date())\n            .find((part) => part.type === 'timeZoneName')?.value || '';\n\n        const value = offset.replace('UTC', 'GMT');\n\n        return [\n          value, // key for Map to ensure uniqueness\n          {\n            value,\n            label: `${value} (${tz})`\n          }\n        ];\n      })\n    ).values()\n  ).sort((a, b) => a.value.localeCompare(b.value, undefined, { numeric: true }))\n];\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'One Year Difference',\n    description: 'Calculate the time between dates that are one year apart',\n    sampleOptions: {\n      startDate: '2023-01-01',\n      startTime: '12:00',\n      endDate: '2024-01-01',\n      endTime: '12:00',\n      startTimezone: 'local',\n      endTimezone: 'local'\n    },\n    sampleResult: '1 year'\n  },\n  {\n    title: 'Different Timezones',\n    description: 'Calculate the time difference between New York and London',\n    sampleOptions: {\n      startDate: '2023-01-01',\n      startTime: '12:00',\n      endDate: '2023-01-01',\n      endTime: '12:00',\n      startTimezone: 'GMT-5',\n      endTimezone: 'GMT'\n    },\n    sampleResult: '5 hours'\n  },\n  {\n    title: 'Detailed Time Breakdown',\n    description: 'Show a detailed breakdown of a time difference',\n    sampleOptions: {\n      startDate: '2023-01-01',\n      startTime: '09:30',\n      endDate: '2023-01-03',\n      endTime: '14:45',\n      startTimezone: 'local',\n      endTimezone: 'local'\n    },\n    sampleResult: '2 days, 5 hours, 15 minutes'\n  }\n];\n\nexport default function TimeBetweenDates() {\n  const { t } = useTranslation('time');\n  const [result, setResult] = useState<string>('');\n\n  return (\n    <ToolContent\n      title={t('timeBetweenDates.title')}\n      inputComponent={null}\n      resultComponent={\n        result ? (\n          <Paper\n            elevation={3}\n            sx={{\n              p: 3,\n              borderLeft: '5px solid',\n              borderColor: 'primary.main',\n              bgcolor: 'background.paper',\n              maxWidth: '100%',\n              mx: 'auto'\n            }}\n          >\n            <Typography\n              variant=\"h4\"\n              align=\"center\"\n              sx={{ fontWeight: 'bold', color: 'primary.main' }}\n            >\n              {result}\n            </Typography>\n          </Paper>\n        ) : null\n      }\n      initialValues={initialValues}\n      validationSchema={validationSchema}\n      exampleCards={exampleCards}\n      toolInfo={{\n        title: t('timeBetweenDates.toolInfo.title'),\n        description: t('timeBetweenDates.toolInfo.description')\n      }}\n      getGroups={({ values, updateField }) => [\n        {\n          title: t('timeBetweenDates.startDateTime'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                description={t('timeBetweenDates.startDate')}\n                value={values.startDate}\n                onOwnChange={(val) => updateField('startDate', val)}\n                type=\"date\"\n              />\n              <TextFieldWithDesc\n                description={t('timeBetweenDates.startTime')}\n                value={values.startTime}\n                onOwnChange={(val) => updateField('startTime', val)}\n                type=\"time\"\n              />\n              <SelectWithDesc\n                description={t('timeBetweenDates.startTimezone')}\n                selected={values.startTimezone}\n                onChange={(val: string) => updateField('startTimezone', val)}\n                options={timezoneOptions}\n              />\n            </Box>\n          )\n        },\n        {\n          title: t('timeBetweenDates.endDateTime'),\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                description={t('timeBetweenDates.endDate')}\n                value={values.endDate}\n                onOwnChange={(val) => updateField('endDate', val)}\n                type=\"date\"\n              />\n              <TextFieldWithDesc\n                description={t('timeBetweenDates.endTime')}\n                value={values.endTime}\n                onOwnChange={(val) => updateField('endTime', val)}\n                type=\"time\"\n              />\n              <SelectWithDesc\n                description={t('timeBetweenDates.endTimezone')}\n                selected={values.endTimezone}\n                onChange={(val: string) => updateField('endTimezone', val)}\n                options={timezoneOptions}\n              />\n            </Box>\n          )\n        }\n      ]}\n      compute={(values) => {\n        try {\n          const startDateTime = getTimeWithTimezone(\n            values.startDate,\n            values.startTime,\n            values.startTimezone\n          );\n\n          const endDateTime = getTimeWithTimezone(\n            values.endDate,\n            values.endTime,\n            values.endTimezone\n          );\n\n          // Calculate time difference\n          const difference = calculateTimeBetweenDates(\n            startDateTime,\n            endDateTime\n          );\n\n          // Auto-determine the best unit to display based on the time difference\n          const bestUnit: TimeUnit =\n            unitHierarchy.find((unit) => difference[unit] > 0) ||\n            'milliseconds';\n\n          const formattedDifference = formatTimeWithLargestUnit(\n            difference,\n            bestUnit\n          );\n\n          setResult(formattedDifference);\n        } catch (error) {\n          setResult(\n            `Error: ${\n              error instanceof Error\n                ? error.message\n                : 'Failed to calculate time difference'\n            }`\n          );\n        }\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/time/time-between-dates/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('time', {\n  path: 'time-between-dates',\n  icon: 'material-symbols:schedule',\n\n  keywords: ['dates', 'time', 'difference', 'duration', 'calculate'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'time:timeBetweenDates.title',\n    description: 'time:timeBetweenDates.description',\n    shortDescription: 'time:timeBetweenDates.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/time/time-between-dates/service.ts",
    "content": "import dayjs from 'dayjs';\nimport utc from 'dayjs/plugin/utc';\nimport timezone from 'dayjs/plugin/timezone';\nimport duration from 'dayjs/plugin/duration';\n\ndayjs.extend(utc);\ndayjs.extend(timezone);\ndayjs.extend(duration);\n\nexport const unitHierarchy = [\n  'years',\n  'months',\n  'days',\n  'hours',\n  'minutes',\n  'seconds',\n  'milliseconds'\n] as const;\n\nexport type TimeUnit = (typeof unitHierarchy)[number];\nexport type TimeDifference = Record<TimeUnit, number>;\n\n// Mapping common abbreviations to IANA time zone names\nexport const tzMap: { [abbr: string]: string } = {\n  EST: 'America/New_York',\n  EDT: 'America/New_York',\n  CST: 'America/Chicago',\n  CDT: 'America/Chicago',\n  MST: 'America/Denver',\n  MDT: 'America/Denver',\n  PST: 'America/Los_Angeles',\n  PDT: 'America/Los_Angeles',\n  GMT: 'Etc/GMT',\n  UTC: 'Etc/UTC'\n  // add more mappings as needed\n};\n\n// Parse a date string with a time zone abbreviation,\n// e.g. \"02/02/2024 14:55 EST\"\nexport const parseWithTZ = (dateTimeStr: string): dayjs.Dayjs => {\n  const parts = dateTimeStr.trim().split(' ');\n  const tzAbbr = parts.pop()!; // extract the timezone part (e.g., EST)\n  const dateTimePart = parts.join(' ');\n  const tzName = tzMap[tzAbbr];\n  if (!tzName) {\n    throw new Error(`Timezone abbreviation ${tzAbbr} not supported`);\n  }\n  // Parse using the format \"MM/DD/YYYY HH:mm\" in the given time zone\n  return dayjs.tz(dateTimePart, 'MM/DD/YYYY HH:mm', tzName);\n};\n\nexport const calculateTimeBetweenDates = (\n  startDate: Date,\n  endDate: Date\n): TimeDifference => {\n  let start = dayjs(startDate);\n  let end = dayjs(endDate);\n\n  // Swap dates if start is after end\n  if (end.isBefore(start)) {\n    [start, end] = [end, start];\n  }\n\n  // Calculate each unit incrementally so that the remainder is applied for subsequent units.\n  const years = end.diff(start, 'year');\n  const startPlusYears = start.add(years, 'year');\n\n  const months = end.diff(startPlusYears, 'month');\n  const startPlusMonths = startPlusYears.add(months, 'month');\n\n  const days = end.diff(startPlusMonths, 'day');\n  const startPlusDays = startPlusMonths.add(days, 'day');\n\n  const hours = end.diff(startPlusDays, 'hour');\n  const startPlusHours = startPlusDays.add(hours, 'hour');\n\n  const minutes = end.diff(startPlusHours, 'minute');\n  const startPlusMinutes = startPlusHours.add(minutes, 'minute');\n\n  const seconds = end.diff(startPlusMinutes, 'second');\n  const startPlusSeconds = startPlusMinutes.add(seconds, 'second');\n\n  const milliseconds = end.diff(startPlusSeconds, 'millisecond');\n\n  return {\n    years,\n    months,\n    days,\n    hours,\n    minutes,\n    seconds,\n    milliseconds\n  };\n};\n\n// Calculate duration between two date strings with timezone abbreviations\nexport const getDuration = (\n  startStr: string,\n  endStr: string\n): TimeDifference => {\n  const start = parseWithTZ(startStr);\n  const end = parseWithTZ(endStr);\n\n  if (end.isBefore(start)) {\n    throw new Error('End date must be after start date');\n  }\n\n  return calculateTimeBetweenDates(start.toDate(), end.toDate());\n};\n\nexport const formatTimeDifference = (\n  difference: TimeDifference,\n  includeUnits: TimeUnit[] = unitHierarchy.slice(0, -2)\n): string => {\n  // First normalize the values (convert 24 hours to 1 day, etc.)\n  const normalized = { ...difference };\n\n  // Convert milliseconds to seconds\n  if (normalized.milliseconds >= 1000) {\n    const additionalSeconds = Math.floor(normalized.milliseconds / 1000);\n    normalized.seconds += additionalSeconds;\n    normalized.milliseconds %= 1000;\n  }\n\n  // Convert seconds to minutes\n  if (normalized.seconds >= 60) {\n    const additionalMinutes = Math.floor(normalized.seconds / 60);\n    normalized.minutes += additionalMinutes;\n    normalized.seconds %= 60;\n  }\n\n  // Convert minutes to hours\n  if (normalized.minutes >= 60) {\n    const additionalHours = Math.floor(normalized.minutes / 60);\n    normalized.hours += additionalHours;\n    normalized.minutes %= 60;\n  }\n\n  // Convert hours to days if 24 or more\n  if (normalized.hours >= 24) {\n    const additionalDays = Math.floor(normalized.hours / 24);\n    normalized.days += additionalDays;\n    normalized.hours %= 24;\n  }\n\n  const timeUnits: { key: TimeUnit; value: number; label: string }[] = [\n    { key: 'years', value: normalized.years, label: 'year' },\n    { key: 'months', value: normalized.months, label: 'month' },\n    { key: 'days', value: normalized.days, label: 'day' },\n    { key: 'hours', value: normalized.hours, label: 'hour' },\n    { key: 'minutes', value: normalized.minutes, label: 'minute' },\n    { key: 'seconds', value: normalized.seconds, label: 'second' },\n    {\n      key: 'milliseconds',\n      value: normalized.milliseconds,\n      label: 'millisecond'\n    }\n  ];\n\n  const parts = timeUnits\n    .filter(({ key }) => includeUnits.includes(key))\n    .map(({ value, label }) => {\n      if (value === 0) return '';\n      return `${value} ${label}${value === 1 ? '' : 's'}`;\n    })\n    .filter(Boolean);\n\n  if (parts.length === 0) {\n    return '0 minutes';\n  }\n\n  return parts.join(', ');\n};\n\nexport const getTimeWithTimezone = (\n  dateString: string,\n  timeString: string,\n  timezone: string\n): Date => {\n  // If timezone is \"local\", return the local date\n  if (timezone === 'local') {\n    const dateTimeString = `${dateString}T${timeString}`;\n    return dayjs(dateTimeString).toDate();\n  }\n\n  // Check if the timezone is a known abbreviation\n  if (tzMap[timezone]) {\n    const dateTimeString = `${dateString} ${timeString}`;\n    return dayjs\n      .tz(dateTimeString, 'YYYY-MM-DD HH:mm', tzMap[timezone])\n      .toDate();\n  }\n\n  // Handle GMT+/- format\n  const match = timezone.match(/^GMT(?:([+-]\\d{1,2})(?::(\\d{2}))?)?$/);\n  if (!match) {\n    throw new Error('Invalid timezone format');\n  }\n\n  const dateTimeString = `${dateString}T${timeString}Z`;\n  const utcDate = dayjs.utc(dateTimeString);\n\n  if (!utcDate.isValid()) {\n    throw new Error('Invalid date or time format');\n  }\n\n  const offsetHours = match[1] ? parseInt(match[1], 10) : 0;\n  const offsetMinutes = match[2] ? parseInt(match[2], 10) : 0;\n  const totalOffsetMinutes =\n    offsetHours * 60 + (offsetHours < 0 ? -offsetMinutes : offsetMinutes);\n\n  return utcDate.subtract(totalOffsetMinutes, 'minute').toDate();\n};\n\nexport const formatTimeWithLargestUnit = (\n  difference: TimeDifference,\n  largestUnit: TimeUnit\n): string => {\n  const largestUnitIndex = unitHierarchy.indexOf(largestUnit);\n  const unitsToInclude = unitHierarchy.slice(\n    largestUnitIndex,\n    unitHierarchy.length // Include milliseconds if it's the largest unit requested\n  );\n  return formatTimeDifference(difference, unitsToInclude);\n};\n"
  },
  {
    "path": "src/pages/tools/time/time-between-dates/time-between-dates.service.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport dayjs from 'dayjs';\nimport utc from 'dayjs/plugin/utc';\nimport timezone from 'dayjs/plugin/timezone';\nimport duration from 'dayjs/plugin/duration';\nimport {\n  calculateTimeBetweenDates,\n  formatTimeDifference,\n  formatTimeWithLargestUnit,\n  getTimeWithTimezone\n} from './service';\n\ndayjs.extend(utc);\ndayjs.extend(timezone);\ndayjs.extend(duration);\n\n// Utility function to create a date\nconst createDate = (\n  year: number,\n  month: number,\n  day: number,\n  hours = 0,\n  minutes = 0\n) => dayjs.utc(Date.UTC(year, month - 1, day, hours, minutes)).toDate();\n\ndescribe('calculateTimeBetweenDates', () => {\n  it('should calculate exactly 1 year difference', () => {\n    const startDate = createDate(2023, 1, 1);\n    const endDate = createDate(2024, 1, 1);\n    const result = calculateTimeBetweenDates(startDate, endDate);\n\n    expect(result.years).toBe(1);\n    expect(result.months).toBe(0);\n    expect(result.days).toBe(0);\n    expect(result.hours).toBe(0);\n    expect(result.minutes).toBe(0);\n  });\n\n  it('should calculate 1 year and 1 day difference', () => {\n    const startDate = createDate(2023, 1, 1);\n    const endDate = createDate(2024, 1, 2);\n    const result = calculateTimeBetweenDates(startDate, endDate);\n\n    expect(result.years).toBe(1);\n    expect(result.months).toBe(0);\n    expect(result.days).toBe(1);\n    expect(result.hours).toBe(0);\n    expect(result.minutes).toBe(0);\n  });\n\n  it('should handle leap year correctly', () => {\n    const startDate = createDate(2024, 2, 28); // February 28th in leap year\n    const endDate = createDate(2024, 3, 1); // March 1st\n    const result = calculateTimeBetweenDates(startDate, endDate);\n\n    expect(result.days).toBe(2);\n    expect(result.months).toBe(0);\n    expect(result.years).toBe(0);\n    expect(result.hours).toBe(0);\n    expect(result.minutes).toBe(0);\n  });\n\n  it('should swap dates if startDate is after endDate', () => {\n    const startDate = createDate(2024, 1, 1);\n    const endDate = createDate(2023, 1, 1);\n    const result = calculateTimeBetweenDates(startDate, endDate);\n\n    expect(result.years).toBe(1);\n    expect(result.months).toBe(0);\n    expect(result.days).toBe(0);\n    expect(result.hours).toBe(0);\n    expect(result.minutes).toBe(0);\n  });\n\n  it('should handle same day different hours', () => {\n    const startDate = createDate(2024, 1, 1, 10);\n    const endDate = createDate(2024, 1, 1, 15);\n    const result = calculateTimeBetweenDates(startDate, endDate);\n\n    expect(result.years).toBe(0);\n    expect(result.months).toBe(0);\n    expect(result.days).toBe(0);\n    expect(result.hours).toBe(5);\n    expect(result.minutes).toBe(0);\n  });\n});\n\ndescribe('formatTimeDifference', () => {\n  it('should format time difference correctly', () => {\n    const difference = {\n      years: 1,\n      months: 2,\n      days: 10,\n      hours: 5,\n      minutes: 30,\n      seconds: 0,\n      milliseconds: 0\n    };\n    expect(formatTimeDifference(difference)).toBe(\n      '1 year, 2 months, 10 days, 5 hours, 30 minutes'\n    );\n  });\n\n  it('should handle singular units correctly', () => {\n    const difference = {\n      years: 1,\n      months: 1,\n      days: 1,\n      hours: 1,\n      minutes: 1,\n      seconds: 0,\n      milliseconds: 0\n    };\n    expect(formatTimeDifference(difference)).toBe(\n      '1 year, 1 month, 1 day, 1 hour, 1 minute'\n    );\n  });\n\n  it('should return 0 minutes if all values are zero', () => {\n    expect(\n      formatTimeDifference({\n        years: 0,\n        months: 0,\n        days: 0,\n        hours: 0,\n        minutes: 0,\n        seconds: 0,\n        milliseconds: 0\n      })\n    ).toBe('0 minutes');\n  });\n});\n\ndescribe('getTimeWithTimezone', () => {\n  it('should convert UTC date to specified timezone', () => {\n    const date = getTimeWithTimezone('2025-03-27', '12:00:00', 'GMT+2');\n    expect(date.getUTCHours()).toBe(10); // 12:00 GMT+2 is 10:00 UTC\n  });\n\n  it('should throw error for invalid timezone', () => {\n    expect(() =>\n      getTimeWithTimezone('2025-03-27', '12:00:00', 'INVALID')\n    ).toThrow('Invalid timezone format');\n  });\n});\n\ndescribe('formatTimeWithLargestUnit', () => {\n  it('should format time with the largest unit', () => {\n    const difference = {\n      years: 0,\n      months: 1,\n      days: 15,\n      hours: 12,\n      minutes: 30,\n      seconds: 0,\n      milliseconds: 0\n    };\n    expect(formatTimeWithLargestUnit(difference, 'days')).toBe(\n      '15 days, 12 hours, 30 minutes'\n    );\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/time/truncate-clock-time/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport CheckboxWithDesc from '@components/options/CheckboxWithDesc';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport { truncateClockTime } from './service';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues = {\n  onlySecond: true,\n  zeroPrint: false,\n  zeroPadding: true\n};\ntype InitialValuesType = typeof initialValues;\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Truncate Seconds',\n    description:\n      'In this example, we get rid of the seconds part from several timer values. We select the \"Truncate Only Seconds\" mode and get a list of timer values consisting only of hours and minutes in format \"hh:mm\" (the \":ss\" part is removed).',\n    sampleText: `01:28:06\n07:39:56\n02:12:41\n10:10:38`,\n    sampleResult: `01:28\n07:39\n02:12\n10:10`,\n    sampleOptions: { onlySecond: true, zeroPrint: false, zeroPadding: true }\n  },\n  {\n    title: 'Truncate Minutes and Seconds',\n    description:\n      'This example truncates five clock times to an hour. It drops the minutes and seconds parts and only outputs the hours with zero padding.',\n    sampleText: `04:42:03\n07:09:59\n11:29:16\n21:30:45\n13:03:09`,\n    sampleResult: `04\n07\n11\n21\n13`,\n    sampleOptions: { onlySecond: false, zeroPrint: false, zeroPadding: true }\n  },\n  {\n    title: 'Set Seconds to Zero',\n    description:\n      'In this example, we set the seconds part of each time to zero by first truncating the time to minutes and then appending a zero at the end in place of the truncated seconds. To do this, we switch on the seconds-truncation mode and activate the option to print-zero-time-parts. We also turn off the padding option and get the output time in format \"h:m:s\", where the seconds are always zero so the final format is \"h:m:0\".',\n    sampleText: `17:25:55\n10:16:07\n12:02:09\n06:05:11`,\n    sampleResult: `17:25:0\n10:16:0\n12:2:0\n6:5:0`,\n    sampleOptions: { onlySecond: true, zeroPrint: true, zeroPadding: true }\n  }\n];\n\nexport default function TruncateClockTime({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('time');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (optionsValues: typeof initialValues, input: string) => {\n    setResult(\n      truncateClockTime(\n        input,\n        optionsValues.onlySecond,\n        optionsValues.zeroPrint,\n        optionsValues.zeroPadding\n      )\n    );\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('truncateClockTime.truncationSide'),\n      component: (\n        <Box>\n          <SimpleRadio\n            onClick={() => updateField('onlySecond', true)}\n            checked={values.onlySecond}\n            title={t('truncateClockTime.truncateOnlySeconds')}\n            description={t('truncateClockTime.truncateOnlySecondsDescription')}\n          />\n          <SimpleRadio\n            onClick={() => updateField('onlySecond', false)}\n            checked={!values.onlySecond}\n            title={t('truncateClockTime.truncateMinutesAndSeconds')}\n            description={t(\n              'truncateClockTime.truncateMinutesAndSecondsDescription'\n            )}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('truncateClockTime.printDroppedComponents'),\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            onChange={(val) => updateField('zeroPrint', val)}\n            checked={values.zeroPrint}\n            title={t('truncateClockTime.zeroPrintTruncatedParts')}\n            description={t('truncateClockTime.zeroPrintDescription')}\n          />\n        </Box>\n      )\n    },\n    {\n      title: t('truncateClockTime.timePadding'),\n      component: (\n        <Box>\n          <CheckboxWithDesc\n            onChange={(val) => updateField('zeroPadding', val)}\n            checked={values.zeroPadding}\n            title={t('truncateClockTime.useZeroPadding')}\n            description={t('truncateClockTime.zeroPaddingDescription')}\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolTextInput value={input} onChange={setInput} />}\n      resultComponent={<ToolTextResult value={result} />}\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('truncateClockTime.toolInfo.title', { title }),\n        description: longDescription\n      }}\n      exampleCards={exampleCards}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/time/truncate-clock-time/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('time', {\n  path: 'truncate-clock-time',\n  icon: 'material-symbols:schedule',\n\n  keywords: ['time', 'truncate', 'clock', 'round', 'precision'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'time:truncateClockTime.title',\n    description: 'time:truncateClockTime.description',\n    shortDescription: 'time:truncateClockTime.shortDescription',\n    userTypes: ['generalUsers', 'developers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/time/truncate-clock-time/service.ts",
    "content": "import { containsOnlyDigits } from '@utils/string';\n\nfunction compute(\n  timeArray: string[],\n  lineNumber: number,\n  onlySeconds: boolean,\n  zeroPrint: boolean,\n  zeroPadding: boolean\n): string {\n  if (timeArray[0] == '') {\n    return '';\n  }\n  if (timeArray.length > 3) {\n    throw new Error(`Time contains more than 3 parts on line ${lineNumber}`);\n  }\n  [...timeArray, '0', '0'].forEach((time, index) => {\n    if (!containsOnlyDigits(time)) {\n      throw new Error(\n        `Time doesn't contain valid ${\n          ['hours', 'minutes', 'seconds'][index]\n        } on line ${lineNumber}`\n      );\n    }\n  });\n\n  const slicedArray = onlySeconds\n    ? timeArray.slice(0, 2)\n    : timeArray.slice(0, 1);\n\n  if (zeroPrint) {\n    onlySeconds ? slicedArray.push('0') : slicedArray.push('0', '0');\n  }\n\n  return zeroPadding\n    ? slicedArray\n        .map((unit) => String(unit).padStart(2, '0')) // Ensures two-digit format\n        .join(':')\n    : slicedArray.join(':');\n}\n\nexport function truncateClockTime(\n  input: string,\n  onlySeconds: boolean,\n  zeroPrint: boolean,\n  zeroPadding: boolean\n): string {\n  const result: string[] = [];\n\n  const lines = input.split('\\n');\n\n  lines.forEach((line, index) => {\n    const timeArray = line.split(':');\n    const truncatedTime = compute(\n      timeArray,\n      index + 1,\n      onlySeconds,\n      zeroPrint,\n      zeroPadding\n    );\n    result.push(truncatedTime);\n  });\n\n  return result.join('\\n');\n}\n"
  },
  {
    "path": "src/pages/tools/time/truncate-clock-time/truncate-clock-time.service.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { truncateClockTime } from './service';\n\ndescribe('truncateClockTime', () => {\n  it('should truncate time to hours only without zero padding', () => {\n    const input = '12:34:56\\n07:08:09';\n    const result = truncateClockTime(input, false, false, false);\n    expect(result).toBe('12\\n07');\n  });\n  it('should truncate time to hours only without zero padding 2', () => {\n    const input = '12:34:56\\n7:08:09';\n    const result = truncateClockTime(input, false, false, false);\n    expect(result).toBe('12\\n7');\n  });\n\n  it('should truncate time to hours and minutes with zero padding', () => {\n    const input = '3:4:5\\n7:8:9';\n    const result = truncateClockTime(input, true, false, true);\n    expect(result).toBe('03:04\\n07:08');\n  });\n\n  it('should handle empty input gracefully', () => {\n    const input = '';\n    const result = truncateClockTime(input, false, false, false);\n    expect(result).toBe('');\n  });\n\n  it('should throw an error if time contains more than 3 parts', () => {\n    const input = '12:34:56:78';\n    expect(() => truncateClockTime(input, false, false, false)).toThrow(\n      'Time contains more than 3 parts on line 1'\n    );\n  });\n\n  it('should throw an error if time contains invalid characters', () => {\n    const input = '12:34:ab';\n    expect(() => truncateClockTime(input, false, false, false)).toThrow(\n      \"Time doesn't contain valid seconds on line 1\"\n    );\n  });\n\n  it('should add zero seconds and minutes when zeroPrint is true', () => {\n    const input = '12';\n    const result = truncateClockTime(input, false, true, false);\n    expect(result).toBe('12:0:0');\n  });\n\n  it('should pad single-digit hours, minutes, and seconds when zeroPadding is true', () => {\n    const input = '1:2:3';\n    const result = truncateClockTime(input, true, true, true);\n    expect(result).toBe('01:02:00');\n  });\n\n  it('should handle multiple lines of input correctly', () => {\n    const input = '12:34:56\\n1:2:3\\n7:8';\n    const result = truncateClockTime(input, true, true, true);\n    expect(result).toBe('12:34:00\\n01:02:00\\n07:08:00');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/video/change-speed/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { InitialValuesType } from './types';\nimport ToolVideoInput from '@components/input/ToolVideoInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  newSpeed: 2\n};\n\nexport default function ChangeSpeed({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('video');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  // FFmpeg only supports a tempo between 0.5 and 2.0, so we chain filters\n  const computeAudioFilter = (speed: number): string => {\n    if (speed <= 2 && speed >= 0.5) {\n      return `atempo=${speed}`;\n    }\n\n    // Break into supported chunks\n    const filters: string[] = [];\n    let remainingSpeed = speed;\n    while (remainingSpeed > 2.0) {\n      filters.push('atempo=2.0');\n      remainingSpeed /= 2.0;\n    }\n    while (remainingSpeed < 0.5) {\n      filters.push('atempo=0.5');\n      remainingSpeed /= 0.5;\n    }\n    filters.push(`atempo=${remainingSpeed.toFixed(2)}`);\n\n    return filters.join(',');\n  };\n\n  const compute = (optionsValues: InitialValuesType, input: File | null) => {\n    if (!input) return;\n    const { newSpeed } = optionsValues;\n    let ffmpeg: FFmpeg | null = null;\n    let ffmpegLoaded = false;\n\n    const processVideo = async (\n      file: File,\n      newSpeed: number\n    ): Promise<void> => {\n      if (newSpeed === 0) return;\n      setLoading(true);\n\n      if (!ffmpeg) {\n        ffmpeg = new FFmpeg();\n      }\n\n      if (!ffmpegLoaded) {\n        await ffmpeg.load({\n          wasmURL:\n            'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n        });\n        ffmpegLoaded = true;\n      }\n\n      // Write file to FFmpeg FS\n      const fileName = file.name;\n      const outputName = 'output.mp4';\n\n      try {\n        ffmpeg.writeFile(fileName, await fetchFile(file));\n\n        const videoFilter = `setpts=${1 / newSpeed}*PTS`;\n        const audioFilter = computeAudioFilter(newSpeed);\n\n        // Run FFmpeg command\n        await ffmpeg.exec([\n          '-i',\n          fileName,\n          '-vf',\n          videoFilter,\n          '-filter:a',\n          audioFilter,\n          '-c:v',\n          'libx264',\n          '-preset',\n          'ultrafast',\n          '-c:a',\n          'aac',\n          outputName\n        ]);\n\n        const data = await ffmpeg.readFile(outputName);\n\n        // Create new file from processed data\n        const blob = new Blob([data as any], { type: 'video/mp4' });\n        const newFile = new File(\n          [blob],\n          file.name.replace('.mp4', `-${newSpeed}x.mp4`),\n          { type: 'video/mp4' }\n        );\n\n        // Clean up to free memory\n        await ffmpeg.deleteFile(fileName);\n        await ffmpeg.deleteFile(outputName);\n\n        setResult(newFile);\n      } catch (err) {\n        console.error(`Failed to process video: ${err}`);\n        throw err;\n      } finally {\n        setLoading(false);\n      }\n    };\n\n    // Here we set the output video\n    processVideo(input, newSpeed);\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('changeSpeed.newVideoSpeed'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            value={values.newSpeed.toString()}\n            onOwnChange={(val) => updateField('newSpeed', Number(val))}\n            description={t('changeSpeed.defaultMultiplier')}\n            type=\"number\"\n          />\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolVideoInput\n          value={input}\n          onChange={setInput}\n          title={t('changeSpeed.inputTitle')}\n        />\n      }\n      resultComponent={\n        loading ? (\n          <ToolFileResult\n            title={t('changeSpeed.settingSpeed')}\n            value={null}\n            loading={true}\n          />\n        ) : (\n          <ToolFileResult\n            title={t('changeSpeed.resultTitle')}\n            value={result}\n            extension=\"mp4\"\n          />\n        )\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('changeSpeed.toolInfo.title', { title }),\n        description: longDescription\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/change-speed/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('video', {\n  path: 'change-speed',\n  icon: 'material-symbols:speed',\n\n  keywords: ['video', 'speed', 'playback', 'fast', 'slow'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'video:changeSpeed.title',\n    description: 'video:changeSpeed.description',\n    shortDescription: 'video:changeSpeed.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/video/change-speed/service.ts",
    "content": "import { InitialValuesType } from './types';\n\nexport function main(\n  input: File | null,\n  options: InitialValuesType\n): File | null {\n  return input;\n}\n"
  },
  {
    "path": "src/pages/tools/video/change-speed/types.ts",
    "content": "export type InitialValuesType = {\n  newSpeed: number;\n};\n"
  },
  {
    "path": "src/pages/tools/video/compress/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useCallback, useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { debounce } from 'lodash';\nimport ToolVideoInput from '@components/input/ToolVideoInput';\nimport { compressVideo, VideoResolution } from './service';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport Slider from 'rc-slider';\nimport 'rc-slider/assets/index.css';\nimport { useTranslation } from 'react-i18next';\n\nexport const initialValues = {\n  width: 480 as VideoResolution,\n  crf: 23,\n  preset: 'medium'\n};\n\nexport const validationSchema = Yup.object({\n  width: Yup.number()\n    .oneOf(\n      [240, 360, 480, 720, 1080],\n      'Width must be one of the standard resolutions'\n    )\n    .required('Width is required'),\n  crf: Yup.number()\n    .min(0, 'CRF must be at least 0')\n    .max(51, 'CRF must be at most 51')\n    .required('CRF is required'),\n  preset: Yup.string()\n    .oneOf(\n      [\n        'ultrafast',\n        'superfast',\n        'veryfast',\n        'faster',\n        'fast',\n        'medium',\n        'slow',\n        'slower',\n        'veryslow'\n      ],\n      'Preset must be a valid ffmpeg preset'\n    )\n    .required('Preset is required')\n});\n\nconst resolutionOptions: { value: VideoResolution; label: string }[] = [\n  { value: 240, label: '240p' },\n  { value: 360, label: '360p' },\n  { value: 480, label: '480p' },\n  { value: 720, label: '720p' },\n  { value: 1080, label: '1080p' }\n];\n\nconst presetOptions = [\n  { value: 'ultrafast', label: 'Ultrafast (Lowest Quality, Smallest Size)' },\n  { value: 'superfast', label: 'Superfast' },\n  { value: 'veryfast', label: 'Very Fast' },\n  { value: 'faster', label: 'Faster' },\n  { value: 'fast', label: 'Fast' },\n  { value: 'medium', label: 'Medium (Balanced)' },\n  { value: 'slow', label: 'Slow' },\n  { value: 'slower', label: 'Slower' },\n  { value: 'veryslow', label: 'Very Slow (Highest Quality, Largest Size)' }\n];\n\nexport default function CompressVideo({ title }: ToolComponentProps) {\n  const { t } = useTranslation('video');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const compute = async (\n    optionsValues: typeof initialValues,\n    input: File | null\n  ) => {\n    if (!input) return;\n    setLoading(true);\n\n    try {\n      const compressedFile = await compressVideo(input, {\n        width: optionsValues.width,\n        crf: optionsValues.crf,\n        preset: optionsValues.preset\n      });\n      setResult(compressedFile);\n    } catch (error) {\n      console.error('Error compressing video:', error);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const debouncedCompute = useCallback(debounce(compute, 1000), []);\n\n  const getGroups: GetGroupsType<typeof initialValues> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('compress.resolution'),\n      component: (\n        <Box>\n          {resolutionOptions.map((option) => (\n            <SimpleRadio\n              key={option.value}\n              title={option.label}\n              checked={values.width === option.value}\n              onClick={() => {\n                updateField('width', option.value);\n              }}\n            />\n          ))}\n        </Box>\n      )\n    },\n    {\n      title: t('compress.quality'),\n      component: (\n        <Box sx={{ mb: 2 }}>\n          <Slider\n            min={0}\n            max={51}\n            style={{ width: '90%' }}\n            value={values.crf}\n            onChange={(value) => {\n              updateField('crf', typeof value === 'number' ? value : value[0]);\n            }}\n            marks={{\n              0: t('compress.lossless'),\n              23: t('compress.default'),\n              51: t('compress.worst')\n            }}\n          />\n        </Box>\n      )\n    }\n    // {\n    //   title: 'Encoding Preset',\n    //   component: (\n    //     <SelectWithDesc\n    //       selected={values.preset}\n    //       onChange={(value) => updateField('preset', value)}\n    //       options={presetOptions}\n    //       description={\n    //         'Determines the compression speed. Slower presets provide better compression (quality per filesize) but take more time.'\n    //       }\n    //     />\n    //   )\n    // }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolVideoInput\n          value={input}\n          onChange={setInput}\n          title={t('compress.inputTitle')}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          title={t('compress.resultTitle')}\n          value={result}\n          extension={'mp4'}\n          loading={loading}\n          loadingText={t('compress.loadingText')}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={debouncedCompute}\n      setInput={setInput}\n      validationSchema={validationSchema}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/compress/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('video', {\n  path: 'compress',\n  icon: 'icon-park-outline:compression',\n\n  keywords: [\n    'compress',\n    'video',\n    'reduce',\n    'size',\n    'optimize',\n    'mp4',\n    'mov',\n    'avi',\n    'video editing',\n    'shrink'\n  ],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'video:compress.title',\n    description: 'video:compress.description',\n    shortDescription: 'video:compress.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/video/compress/service.ts",
    "content": "import { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\n\nconst ffmpeg = new FFmpeg();\n\nexport type VideoResolution = 240 | 360 | 480 | 720 | 1080;\n\nexport interface CompressVideoOptions {\n  width: VideoResolution;\n  crf: number; // Constant Rate Factor (quality): lower = better quality, higher = smaller file\n  preset: string; // Encoding speed preset\n}\n\nexport async function compressVideo(\n  input: File,\n  options: CompressVideoOptions\n): Promise<File> {\n  if (!ffmpeg.loaded) {\n    await ffmpeg.load({\n      wasmURL:\n        'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n    });\n  }\n\n  const inputName = 'input.mp4';\n  const outputName = 'output.mp4';\n\n  await ffmpeg.writeFile(inputName, await fetchFile(input));\n\n  // Calculate height as -1 to maintain aspect ratio\n  const scaleFilter = `scale=${options.width}:-2`;\n\n  const args = [\n    '-i',\n    inputName,\n    '-vf',\n    scaleFilter,\n    '-c:v',\n    'libx264',\n    '-crf',\n    options.crf.toString(),\n    '-preset',\n    options.preset,\n    '-c:a',\n    'aac', // Copy audio stream\n    outputName\n  ];\n\n  try {\n    await ffmpeg.exec(args);\n  } catch (error) {\n    console.error('FFmpeg execution failed:', error);\n  }\n  const compressedData = await ffmpeg.readFile(outputName);\n  return new File(\n    [new Blob([compressedData as any], { type: 'video/mp4' })],\n    `${input.name.replace(/\\.[^/.]+$/, '')}_compressed_${options.width}p.mp4`,\n    { type: 'video/mp4' }\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/crop-video/index.tsx",
    "content": "import { Box, Typography, TextField, Alert } from '@mui/material';\nimport React, { useState, useCallback } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { cropVideo, getVideoDimensions } from './service';\nimport { InitialValuesType } from './types';\nimport ToolVideoInput from '@components/input/ToolVideoInput';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport { debounce } from 'lodash';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  x: 0,\n  y: 0,\n  width: 100,\n  height: 100\n};\n\nexport default function CropVideo({ title }: ToolComponentProps) {\n  const { t } = useTranslation('video');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n  const [videoDimensions, setVideoDimensions] = useState<{\n    width: number;\n    height: number;\n  } | null>(null);\n  const [processingError, setProcessingError] = useState<string>('');\n\n  const validateDimensions = (values: InitialValuesType): string => {\n    if (!videoDimensions) return '';\n\n    if (values.x < 0 || values.y < 0) {\n      return t('cropVideo.errorNonNegativeCoordinates');\n    }\n\n    if (values.width <= 0 || values.height <= 0) {\n      return t('cropVideo.errorPositiveDimensions');\n    }\n\n    if (values.x + values.width > videoDimensions.width) {\n      return t('cropVideo.errorBeyondWidth', {\n        width: videoDimensions.width\n      });\n    }\n\n    if (values.y + values.height > videoDimensions.height) {\n      return t('cropVideo.errorBeyondHeight', {\n        height: videoDimensions.height\n      });\n    }\n\n    return '';\n  };\n\n  const compute = async (\n    optionsValues: InitialValuesType,\n    input: File | null\n  ) => {\n    if (!input) return;\n\n    const error = validateDimensions(optionsValues);\n    if (error) {\n      setProcessingError(error);\n      return;\n    }\n\n    setProcessingError('');\n    setLoading(true);\n\n    try {\n      const croppedFile = await cropVideo(input, optionsValues);\n      setResult(croppedFile);\n    } catch (error) {\n      console.error('Error cropping video:', error);\n      setProcessingError(t('cropVideo.errorCroppingVideo'));\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  // 2 seconds to avoid starting job half way through\n  const debouncedCompute = useCallback(debounce(compute, 2000), [\n    videoDimensions\n  ]);\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('cropVideo.videoInformation'),\n      component: (\n        <Box>\n          {videoDimensions ? (\n            <Typography variant=\"body2\" sx={{ mb: 2 }}>\n              {t('cropVideo.videoDimensions', {\n                width: videoDimensions.width,\n                height: videoDimensions.height\n              })}\n            </Typography>\n          ) : (\n            <Typography variant=\"body2\" sx={{ mb: 2 }}>\n              {t('cropVideo.loadVideoForDimensions')}\n            </Typography>\n          )}\n        </Box>\n      )\n    },\n    {\n      title: t('cropVideo.cropCoordinates'),\n      component: (\n        <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n          {processingError && (\n            <Alert severity=\"error\" sx={{ mb: 2 }}>\n              {processingError}\n            </Alert>\n          )}\n          <Box sx={{ display: 'flex', gap: 2 }}>\n            <TextField\n              label={t('cropVideo.xCoordinate')}\n              type=\"number\"\n              value={values.x}\n              onChange={(e) => updateField('x', parseInt(e.target.value) || 0)}\n              size=\"small\"\n              inputProps={{ min: 0 }}\n            />\n            <TextField\n              label={t('cropVideo.yCoordinate')}\n              type=\"number\"\n              value={values.y}\n              onChange={(e) => updateField('y', parseInt(e.target.value) || 0)}\n              size=\"small\"\n              inputProps={{ min: 0 }}\n            />\n          </Box>\n          <Box sx={{ display: 'flex', gap: 2 }}>\n            <TextField\n              label={t('cropVideo.width')}\n              type=\"number\"\n              value={values.width}\n              onChange={(e) =>\n                updateField('width', parseInt(e.target.value) || 0)\n              }\n              size=\"small\"\n              inputProps={{ min: 1 }}\n            />\n            <TextField\n              label={t('cropVideo.height')}\n              type=\"number\"\n              value={values.height}\n              onChange={(e) =>\n                updateField('height', parseInt(e.target.value) || 0)\n              }\n              size=\"small\"\n              inputProps={{ min: 1 }}\n            />\n          </Box>\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      renderCustomInput={(values, setFieldValue) => (\n        <ToolVideoInput\n          value={input}\n          onChange={(video) => {\n            if (video) {\n              getVideoDimensions(video)\n                .then((dimensions) => {\n                  const newOptions: InitialValuesType = {\n                    x: dimensions.width / 4,\n                    y: dimensions.height / 4,\n                    width: dimensions.width / 2,\n                    height: dimensions.height / 2\n                  };\n                  setFieldValue('x', newOptions.x);\n                  setFieldValue('y', newOptions.y);\n                  setFieldValue('width', newOptions.width);\n                  setFieldValue('height', newOptions.height);\n\n                  setVideoDimensions(dimensions);\n                  setProcessingError('');\n                })\n                .catch((error) => {\n                  console.error('Error getting video dimensions:', error);\n                  setProcessingError(t('cropVideo.errorLoadingDimensions'));\n                });\n            } else {\n              setVideoDimensions(null);\n              setProcessingError('');\n            }\n            setInput(video);\n          }}\n          title={t('cropVideo.inputTitle')}\n        />\n      )}\n      resultComponent={\n        loading ? (\n          <ToolFileResult\n            title={t('cropVideo.croppingVideo')}\n            value={null}\n            loading={true}\n            extension={''}\n          />\n        ) : (\n          <ToolFileResult\n            title={t('cropVideo.resultTitle')}\n            value={result}\n            extension={'mp4'}\n          />\n        )\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={debouncedCompute}\n      setInput={setInput}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/crop-video/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('video', {\n  path: 'crop-video',\n  icon: 'material-symbols:crop',\n  keywords: [\n    'crop',\n    'video',\n    'trim',\n    'aspect ratio',\n    'mp4',\n    'mov',\n    'avi',\n    'video editing',\n    'resize'\n  ],\n  i18n: {\n    name: 'video:cropVideo.title',\n    description: 'video:cropVideo.description',\n    shortDescription: 'video:cropVideo.shortDescription',\n    userTypes: ['generalUsers']\n  },\n  component: lazy(() => import('./index'))\n});\n"
  },
  {
    "path": "src/pages/tools/video/crop-video/service.ts",
    "content": "import { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\nimport { InitialValuesType } from './types';\n\nconst ffmpeg = new FFmpeg();\n\nexport async function getVideoDimensions(\n  file: File\n): Promise<{ width: number; height: number }> {\n  return new Promise((resolve, reject) => {\n    const video = document.createElement('video');\n    const url = URL.createObjectURL(file);\n\n    video.onloadedmetadata = () => {\n      URL.revokeObjectURL(url);\n      resolve({\n        width: video.videoWidth,\n        height: video.videoHeight\n      });\n    };\n\n    video.onerror = () => {\n      URL.revokeObjectURL(url);\n      reject(new Error('Failed to load video metadata'));\n    };\n\n    video.src = url;\n  });\n}\n\nexport async function cropVideo(\n  input: File,\n  options: InitialValuesType\n): Promise<File> {\n  if (!ffmpeg.loaded) {\n    await ffmpeg.load({\n      wasmURL:\n        'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n    });\n  }\n\n  const inputName = 'input.mp4';\n  const outputName = 'output.mp4';\n  await ffmpeg.writeFile(inputName, await fetchFile(input));\n\n  const args = [];\n\n  if (options.width <= 0 || options.height <= 0) {\n    throw new Error('Width and height must be positive');\n  }\n\n  args.push('-i', inputName);\n  args.push(\n    '-vf',\n    `crop=${options.width}:${options.height}:${options.x}:${options.y}`\n  );\n  args.push('-c:v', 'libx264', '-preset', 'ultrafast', outputName);\n\n  await ffmpeg.exec(args);\n\n  const croppedData = await ffmpeg.readFile(outputName);\n  return await new File(\n    [new Blob([croppedData as any], { type: 'video/mp4' })],\n    `${input.name.replace(/\\.[^/.]+$/, '')}_cropped.mp4`,\n    { type: 'video/mp4' }\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/crop-video/types.ts",
    "content": "export type InitialValuesType = {\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n};\n"
  },
  {
    "path": "src/pages/tools/video/flip/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport { useCallback, useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { debounce } from 'lodash';\nimport ToolVideoInput from '@components/input/ToolVideoInput';\nimport { flipVideo } from './service';\nimport { FlipOrientation, InitialValuesType } from './types';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport { useTranslation } from 'react-i18next';\n\nexport const initialValues: InitialValuesType = {\n  orientation: 'horizontal'\n};\n\nexport const validationSchema = Yup.object({\n  orientation: Yup.string()\n    .oneOf(\n      ['horizontal', 'vertical'],\n      'Orientation must be horizontal or vertical'\n    )\n    .required('Orientation is required')\n});\n\nconst orientationOptions: { value: FlipOrientation; label: string }[] = [\n  { value: 'horizontal', label: 'Horizontal (Mirror)' },\n  { value: 'vertical', label: 'Vertical (Upside Down)' }\n];\n\nexport default function FlipVideo({ title }: ToolComponentProps) {\n  const { t } = useTranslation('video');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const compute = async (\n    optionsValues: InitialValuesType,\n    input: File | null\n  ) => {\n    if (!input) return;\n    setLoading(true);\n\n    try {\n      const flippedFile = await flipVideo(input, optionsValues.orientation);\n      setResult(flippedFile);\n    } catch (error) {\n      console.error('Error flipping video:', error);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const debouncedCompute = useCallback(debounce(compute, 1000), []);\n\n  const getGroups: GetGroupsType<InitialValuesType> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('flip.orientation'),\n      component: (\n        <Box>\n          {orientationOptions.map((orientationOption) => (\n            <SimpleRadio\n              key={orientationOption.value}\n              title={t(`flip.${orientationOption.value}Label`)}\n              checked={values.orientation === orientationOption.value}\n              onClick={() => {\n                updateField('orientation', orientationOption.value);\n              }}\n            />\n          ))}\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolVideoInput\n          value={input}\n          onChange={setInput}\n          title={t('flip.inputTitle')}\n        />\n      }\n      resultComponent={\n        loading ? (\n          <ToolFileResult\n            title={t('flip.flippingVideo')}\n            value={null}\n            loading={true}\n            extension={''}\n          />\n        ) : (\n          <ToolFileResult\n            title={t('flip.resultTitle')}\n            value={result}\n            extension={'mp4'}\n          />\n        )\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={debouncedCompute}\n      setInput={setInput}\n      validationSchema={validationSchema}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/flip/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('video', {\n  path: 'flip',\n  icon: 'material-symbols:flip',\n\n  keywords: ['video', 'flip', 'mirror', 'horizontal', 'vertical'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'video:flip.title',\n    description: 'video:flip.description',\n    shortDescription: 'video:flip.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/video/flip/service.ts",
    "content": "import { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\nimport { FlipOrientation } from './types';\n\nconst ffmpeg = new FFmpeg();\n\nexport async function flipVideo(\n  input: File,\n  orientation: FlipOrientation\n): Promise<File> {\n  if (!ffmpeg.loaded) {\n    await ffmpeg.load({\n      wasmURL:\n        'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n    });\n  }\n\n  const inputName = 'input.mp4';\n  const outputName = 'output.mp4';\n  await ffmpeg.writeFile(inputName, await fetchFile(input));\n\n  const flipMap: Record<FlipOrientation, string> = {\n    horizontal: 'hflip',\n    vertical: 'vflip'\n  };\n  const flipFilter = flipMap[orientation];\n\n  const args = ['-i', inputName];\n  if (flipFilter) {\n    args.push('-vf', flipFilter);\n  }\n\n  args.push('-c:v', 'libx264', '-preset', 'ultrafast', outputName);\n\n  await ffmpeg.exec(args);\n\n  const flippedData = await ffmpeg.readFile(outputName);\n  return new File(\n    [new Blob([flippedData as any], { type: 'video/mp4' })],\n    `${input.name.replace(/\\.[^/.]+$/, '')}_flipped.mp4`,\n    { type: 'video/mp4' }\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/flip/types.ts",
    "content": "export type FlipOrientation = 'horizontal' | 'vertical';\n\nexport type InitialValuesType = {\n  orientation: FlipOrientation;\n};\n"
  },
  {
    "path": "src/pages/tools/video/gif/change-speed/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport TextFieldWithDesc from 'components/options/TextFieldWithDesc';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolImageInput from '@components/input/ToolImageInput';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\n\nconst initialValues = {\n  newSpeed: 2\n};\nexport default function ChangeSpeed({ title }: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n\n  const compute = (optionsValues: typeof initialValues, input: File | null) => {\n    if (!input) return;\n    const { newSpeed } = optionsValues;\n    // Initialize FFmpeg once in your component/app\n    let ffmpeg: FFmpeg | null = null;\n    let ffmpegLoaded = false;\n\n    const processImage = async (\n      file: File,\n      newSpeed: number\n    ): Promise<void> => {\n      if (!ffmpeg) {\n        ffmpeg = new FFmpeg();\n      }\n\n      if (!ffmpegLoaded) {\n        await ffmpeg.load({\n          wasmURL:\n            'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n        });\n        ffmpegLoaded = true;\n      }\n\n      try {\n        await ffmpeg.writeFile('input.gif', await fetchFile(file));\n\n        // Process the GIF to change playback speed while preserving quality\n        // The filter_complex does three main operations:\n        // 1. [0:v]setpts=${1/newSpeed}*PTS - Adjusts frame timing:\n        //    - PTS (Presentation Time Stamp) controls when each frame is displayed\n        //    - Dividing by speed factor (e.g., 2 for 2x speed) reduces display time\n        //    - Example: 1/2 = 0.5 → frames show for half their normal duration\n        // 2. split[a][b] - Creates two identical streams for parallel processing:\n        //    - [a] goes to palettegen to create an optimized color palette\n        //    - [b] contains the speed-adjusted frames\n        // 3. [b][p]paletteuse - Applies the generated palette to maintain:\n        //    - Color accuracy\n        //    - Transparency handling\n        //    - Reduced file size\n        // This approach prevents visual artifacts that occur with simple re-encoding\n        await ffmpeg.exec([\n          '-i',\n          'input.gif',\n          '-filter_complex',\n          `[0:v]setpts=${\n            1 / newSpeed\n          }*PTS,split[a][b];[a]palettegen[p];[b][p]paletteuse`,\n          '-f',\n          'gif',\n          'output.gif'\n        ]);\n\n        // Read the result\n        const data = await ffmpeg.readFile('output.gif');\n\n        // Create a new file from the processed data\n        const blob = new Blob([data as any], { type: 'image/gif' });\n        const newFile = new File(\n          [blob],\n          file.name.replace('.gif', `-${newSpeed}x.gif`),\n          {\n            type: 'image/gif'\n          }\n        );\n\n        // Clean up to free memory\n        await ffmpeg.deleteFile('input.gif');\n        await ffmpeg.deleteFile('output.gif');\n\n        setResult(newFile);\n      } catch (error) {\n        console.error('Error processing GIF:', error);\n        throw error;\n      }\n    };\n\n    processImage(input, newSpeed);\n  };\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolImageInput\n          value={input}\n          onChange={setInput}\n          accept={['image/gif']}\n          title={'Input GIF'}\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          title={'Output GIF with new speed'}\n          value={result}\n          extension={'gif'}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={({ values, updateField }) => [\n        {\n          title: 'New GIF speed',\n          component: (\n            <Box>\n              <TextFieldWithDesc\n                value={values.newSpeed}\n                onOwnChange={(val) => updateField('newSpeed', Number(val))}\n                description={'Default multiplier: 2 means 2x faster'}\n                type={'number'}\n              />\n            </Box>\n          )\n        }\n      ]}\n      compute={compute}\n      setInput={setInput}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/gif/change-speed/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n// import image from '@assets/text.png';\n\nexport const tool = defineTool('gif', {\n  path: 'change-speed',\n  icon: 'material-symbols:speed',\n\n  keywords: ['gif', 'speed', 'animation', 'fast', 'slow'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'video:gif.changeSpeed.title',\n    description: 'video:gif.changeSpeed.description',\n    shortDescription: 'video:gif.changeSpeed.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/video/gif/index.ts",
    "content": "import { tool as gifChangeSpeed } from './change-speed/meta';\n\nexport const gifTools = [gifChangeSpeed];\n"
  },
  {
    "path": "src/pages/tools/video/index.ts",
    "content": "import { tool as videoMergeVideo } from './merge-video/meta';\nimport { tool as videoToGif } from './video-to-gif/meta';\nimport { tool as changeSpeed } from './change-speed/meta';\nimport { tool as flipVideo } from './flip/meta';\nimport { gifTools } from './gif';\nimport { tool as trimVideo } from './trim/meta';\nimport { tool as rotateVideo } from './rotate/meta';\nimport { tool as compressVideo } from './compress/meta';\nimport { tool as loopVideo } from './loop/meta';\nimport { tool as cropVideo } from './crop-video/meta';\n\nexport const videoTools = [\n  ...gifTools,\n  trimVideo,\n  rotateVideo,\n  compressVideo,\n  loopVideo,\n  flipVideo,\n  cropVideo,\n  changeSpeed,\n  videoToGif,\n  videoMergeVideo\n];\n"
  },
  {
    "path": "src/pages/tools/video/loop/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { loopVideo } from './service';\nimport { InitialValuesType } from './types';\nimport ToolVideoInput from '@components/input/ToolVideoInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { updateNumberField } from '@utils/string';\nimport * as Yup from 'yup';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {\n  loops: 2\n};\n\nconst validationSchema = Yup.object({\n  loops: Yup.number().min(1, 'Number of loops must be greater than 1')\n});\n\nexport default function Loop({ title, longDescription }: ToolComponentProps) {\n  const { t } = useTranslation('video');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const compute = async (values: InitialValuesType, input: File | null) => {\n    if (!input) return;\n    try {\n      setLoading(true);\n      const resultFile = await loopVideo(input, values);\n      await setResult(resultFile);\n    } catch (error) {\n      console.error(error);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('loop.loops'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            onOwnChange={(value) =>\n              updateNumberField(value, 'loops', updateField)\n            }\n            value={values.loops}\n            label={t('loop.numberOfLoops')}\n          />\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={<ToolVideoInput value={input} onChange={setInput} />}\n      resultComponent={\n        loading ? (\n          <ToolFileResult\n            value={null}\n            title={t('loop.loopingVideo')}\n            loading={true}\n            extension={''}\n          />\n        ) : (\n          <ToolFileResult\n            value={result}\n            title={t('loop.resultTitle')}\n            extension={'mp4'}\n          />\n        )\n      }\n      initialValues={initialValues}\n      validationSchema={validationSchema}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('loop.toolInfo.title', { title }),\n        description: longDescription\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/loop/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('video', {\n  path: 'loop',\n  icon: 'ic:outline-loop',\n\n  keywords: ['video', 'loop', 'repeat', 'continuous'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'video:loop.title',\n    description: 'video:loop.description',\n    shortDescription: 'video:loop.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/video/loop/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\n\nconst ffmpeg = new FFmpeg();\n\nexport async function loopVideo(\n  input: File,\n  options: InitialValuesType\n): Promise<File> {\n  if (!ffmpeg.loaded) {\n    await ffmpeg.load({\n      wasmURL:\n        'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n    });\n  }\n\n  const inputName = 'input.mp4';\n  const outputName = 'output.mp4';\n  await ffmpeg.writeFile(inputName, await fetchFile(input));\n\n  const args = [];\n  const loopCount = options.loops - 1;\n\n  if (loopCount <= 0) {\n    return input;\n  }\n\n  args.push('-stream_loop', loopCount.toString());\n  args.push('-i', inputName);\n  args.push('-c:v', 'libx264', '-preset', 'ultrafast', outputName);\n\n  await ffmpeg.exec(args);\n\n  const loopedData = await ffmpeg.readFile(outputName);\n  return await new File(\n    [new Blob([loopedData as any], { type: 'video/mp4' })],\n    `${input.name.replace(/\\.[^/.]+$/, '')}_looped.mp4`,\n    { type: 'video/mp4' }\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/loop/types.ts",
    "content": "export type InitialValuesType = {\n  loops: number;\n};\n"
  },
  {
    "path": "src/pages/tools/video/merge-video/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport ToolMultipleVideoInput, {\n  MultiVideoInput\n} from '@components/input/ToolMultipleVideoInput';\nimport { mergeVideos } from './service';\nimport { InitialValuesType } from './types';\n\nconst initialValues: InitialValuesType = {};\n\nexport default function MergeVideo({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<MultiVideoInput[]>([]);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const compute = async (\n    _values: InitialValuesType,\n    input: MultiVideoInput[]\n  ) => {\n    if (!input || input.length < 2) {\n      return;\n    }\n    setLoading(true);\n    try {\n      const files = input.map((item) => item.file);\n      const mergedBlob = await mergeVideos(files, initialValues);\n      const mergedFile = new File([mergedBlob], 'merged-video.mp4', {\n        type: 'video/mp4'\n      });\n      setResult(mergedFile);\n    } catch (err) {\n      setResult(null);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolMultipleVideoInput\n          value={input}\n          onChange={(newInput) => {\n            setInput(newInput);\n          }}\n          accept={['video/*', '.mp4', '.avi', '.mov', '.mkv']}\n          title=\"Input Videos\"\n          type=\"video\"\n        />\n      }\n      resultComponent={\n        <ToolFileResult\n          value={result}\n          title={loading ? 'Merging Videos...' : 'Merged Video'}\n          loading={loading}\n          extension={'mp4'}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={null}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/merge-video/merge-video.service.test.ts",
    "content": "import { expect, describe, it, vi } from 'vitest';\n\n// Mock FFmpeg and fetchFile to avoid Node.js compatibility issues\nvi.mock('@ffmpeg/ffmpeg', () => ({\n  FFmpeg: vi.fn().mockImplementation(() => ({\n    loaded: false,\n    load: vi.fn().mockResolvedValue(undefined),\n    writeFile: vi.fn().mockResolvedValue(undefined),\n    exec: vi.fn().mockResolvedValue(undefined),\n    readFile: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])),\n    deleteFile: vi.fn().mockResolvedValue(undefined)\n  }))\n}));\n\nvi.mock('@ffmpeg/util', () => ({\n  fetchFile: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4]))\n}));\n\n// Import after mocking\nimport { mergeVideos } from './service';\n\nfunction createMockFile(name: string, type = 'video/mp4') {\n  return new File([new Uint8Array([0, 1, 2])], name, { type });\n}\n\ndescribe('merge-video', () => {\n  it('throws if less than two files are provided', async () => {\n    await expect(mergeVideos([], {})).rejects.toThrow(\n      'Please provide at least two video files to merge.'\n    );\n    await expect(mergeVideos([createMockFile('a.mp4')], {})).rejects.toThrow(\n      'Please provide at least two video files to merge.'\n    );\n  });\n\n  it('throws if input is not an array', async () => {\n    // @ts-ignore - testing invalid input\n    await expect(mergeVideos(null, {})).rejects.toThrow(\n      'Please provide at least two video files to merge.'\n    );\n  });\n\n  it('successfully merges video files (mocked)', async () => {\n    const mockFile1 = createMockFile('video1.mp4');\n    const mockFile2 = createMockFile('video2.mp4');\n\n    const result = await mergeVideos([mockFile1, mockFile2], {});\n\n    expect(result).toBeInstanceOf(Blob);\n    expect(result.type).toBe('video/mp4');\n  });\n\n  it('handles different video formats by re-encoding', async () => {\n    const mockFile1 = createMockFile('video1.avi', 'video/x-msvideo');\n    const mockFile2 = createMockFile('video2.mov', 'video/quicktime');\n\n    const result = await mergeVideos([mockFile1, mockFile2], {});\n\n    expect(result).toBeInstanceOf(Blob);\n    expect(result.type).toBe('video/mp4');\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/video/merge-video/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('video', {\n  path: 'merge-video',\n  icon: 'fluent:merge-20-regular',\n  keywords: ['merge', 'video', 'append', 'combine'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'video:mergeVideo.title',\n    description: 'video:mergeVideo.description',\n    shortDescription: 'video:mergeVideo.shortDescription',\n    longDescription: 'video:mergeVideo.longDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/video/merge-video/service.ts",
    "content": "import { InitialValuesType, MergeVideoInput, MergeVideoOutput } from './types';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\n\nexport async function mergeVideos(\n  input: MergeVideoInput,\n  options: InitialValuesType\n): Promise<MergeVideoOutput> {\n  if (!Array.isArray(input) || input.length < 2) {\n    throw new Error('Please provide at least two video files to merge.');\n  }\n\n  // Create a new FFmpeg instance for each operation to avoid conflicts\n  const ffmpeg = new FFmpeg();\n\n  const fileNames: string[] = [];\n  const outputName = 'output.mp4';\n\n  try {\n    // Load FFmpeg with proper error handling\n    if (!ffmpeg.loaded) {\n      await ffmpeg.load({\n        wasmURL:\n          'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm',\n        workerURL:\n          'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.worker.js'\n      });\n    }\n\n    // Write all input files to ffmpeg FS with better error handling\n    for (let i = 0; i < input.length; i++) {\n      const fileName = `input${i}.mp4`;\n      fileNames.push(fileName);\n\n      try {\n        const fileData = await fetchFile(input[i]);\n        await ffmpeg.writeFile(fileName, fileData);\n        console.log(`Successfully wrote ${fileName}`);\n      } catch (fileError) {\n        console.error(`Failed to write ${fileName}:`, fileError);\n        throw new Error(`Failed to process input file ${i + 1}: ${fileError}`);\n      }\n    }\n\n    // Build the filter_complex string for concat filter\n    const videoInputs = fileNames.map((_, idx) => `[${idx}:v]`).join(' ');\n    const audioInputs = fileNames.map((_, idx) => `[${idx}:a]`).join(' ');\n    const filterComplex = `${videoInputs} ${audioInputs} concat=n=${input.length}:v=1:a=1 [v] [a]`;\n\n    // Build input arguments\n    const inputArgs = [];\n    for (const fileName of fileNames) {\n      inputArgs.push('-i', fileName);\n    }\n\n    console.log('Starting FFmpeg processing...');\n    console.log('Filter complex:', filterComplex);\n\n    // Method 2: Fallback to concat demuxer\n    try {\n      console.log('Trying concat demuxer method...');\n\n      const concatList = fileNames.map((name) => `file '${name}'`).join('\\n');\n      await ffmpeg.writeFile(\n        'concat_list.txt',\n        new TextEncoder().encode(concatList)\n      );\n\n      await ffmpeg.exec([\n        '-f',\n        'concat',\n        '-safe',\n        '0',\n        '-i',\n        'concat_list.txt',\n        '-c:v',\n        'libx264',\n        '-preset',\n        'ultrafast',\n        '-crf',\n        '30',\n        '-threads',\n        '0',\n        '-y',\n        outputName\n      ]);\n\n      // Check if output was created\n      try {\n        const testRead = await ffmpeg.readFile(outputName);\n        if (testRead && testRead.length > 0) {\n          console.log('Concat demuxer method succeeded');\n        }\n      } catch (readError) {\n        console.log('Concat demuxer method failed to produce output');\n      }\n    } catch (execError) {\n      console.error('Concat demuxer method failed:', execError);\n    }\n\n    // Check if output file exists and read it\n    let mergedData;\n    try {\n      mergedData = await ffmpeg.readFile(outputName);\n      console.log('Successfully read output file');\n    } catch (readError) {\n      console.error('Failed to read output file:', readError);\n      throw new Error('Failed to read merged video file');\n    }\n\n    if (!mergedData || mergedData.length === 0) {\n      throw new Error('Output file is empty or corrupted');\n    }\n\n    return new Blob([mergedData as any], { type: 'video/mp4' });\n  } catch (error) {\n    console.error('Error merging videos:', error);\n    throw error instanceof Error\n      ? error\n      : new Error('Unknown error occurred during video merge');\n  } finally {\n    // Clean up temporary files with better error handling\n    const filesToClean = [...fileNames, outputName, 'concat_list.txt'];\n\n    for (const fileName of filesToClean) {\n      try {\n        await ffmpeg.deleteFile(fileName);\n      } catch (cleanupError) {\n        // Ignore cleanup errors - they're not critical\n        console.warn(`Could not delete ${fileName}:`, cleanupError);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/pages/tools/video/merge-video/types.ts",
    "content": "export type InitialValuesType = {\n  // Add any future options here (e.g., output format, resolution)\n};\n\n// Type for the main function input\nexport type MergeVideoInput = File[];\n\n// Type for the main function output\nexport type MergeVideoOutput = Blob;\n"
  },
  {
    "path": "src/pages/tools/video/rotate/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useCallback, useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport { debounce } from 'lodash';\nimport ToolVideoInput from '@components/input/ToolVideoInput';\nimport { rotateVideo } from './service';\nimport { RotationAngle } from '../../pdf/rotate-pdf/types';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport { useTranslation } from 'react-i18next';\n\nexport const initialValues = {\n  rotation: 90\n};\n\nexport const validationSchema = Yup.object({\n  rotation: Yup.number()\n    .oneOf([0, 90, 180, 270], 'Rotation must be 0, 90, 180, or 270 degrees')\n    .required('Rotation is required')\n});\n\nconst angleOptions: { value: RotationAngle; label: string }[] = [\n  { value: 90, label: '90° Clockwise' },\n  { value: 180, label: '180° (Upside down)' },\n  { value: 270, label: '270° (90° Counter-clockwise)' }\n];\nexport default function RotateVideo({ title }: ToolComponentProps) {\n  const { t } = useTranslation('video');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const compute = async (\n    optionsValues: typeof initialValues,\n    input: File | null\n  ) => {\n    if (!input) return;\n    setLoading(true);\n\n    try {\n      const rotatedFile = await rotateVideo(input, optionsValues.rotation);\n      setResult(rotatedFile);\n    } catch (error) {\n      console.error('Error rotating video:', error);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const debouncedCompute = useCallback(debounce(compute, 1000), []);\n\n  const getGroups: GetGroupsType<typeof initialValues> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('rotate.rotation'),\n      component: (\n        <Box>\n          {angleOptions.map((angleOption) => (\n            <SimpleRadio\n              key={angleOption.value}\n              title={t(`rotate.${angleOption.value}Degrees`)}\n              checked={values.rotation === angleOption.value}\n              onClick={() => {\n                updateField('rotation', angleOption.value);\n              }}\n            />\n          ))}\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolVideoInput\n          value={input}\n          onChange={setInput}\n          title={t('rotate.inputTitle')}\n        />\n      }\n      resultComponent={\n        loading ? (\n          <ToolFileResult\n            title={t('rotate.rotatingVideo')}\n            value={null}\n            loading={true}\n            extension={''}\n          />\n        ) : (\n          <ToolFileResult\n            title={t('rotate.resultTitle')}\n            value={result}\n            extension={'mp4'}\n          />\n        )\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={debouncedCompute}\n      setInput={setInput}\n      validationSchema={validationSchema}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/rotate/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('video', {\n  path: 'rotate',\n  icon: 'material-symbols:rotate-right',\n\n  keywords: ['video', 'rotate', 'orientation', 'degrees'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'video:rotate.title',\n    description: 'video:rotate.description',\n    shortDescription: 'video:rotate.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/video/rotate/service.ts",
    "content": "import { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\n\nconst ffmpeg = new FFmpeg();\n\nexport async function rotateVideo(\n  input: File,\n  rotation: number\n): Promise<File> {\n  if (!ffmpeg.loaded) {\n    await ffmpeg.load({\n      wasmURL:\n        'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n    });\n  }\n\n  const inputName = 'input.mp4';\n  const outputName = 'output.mp4';\n  await ffmpeg.writeFile(inputName, await fetchFile(input));\n\n  const rotateMap: Record<number, string> = {\n    90: 'transpose=1',\n    180: 'transpose=2,transpose=2',\n    270: 'transpose=2',\n    0: ''\n  };\n  const rotateFilter = rotateMap[rotation];\n\n  const args = ['-i', inputName];\n  if (rotateFilter) {\n    args.push('-vf', rotateFilter);\n  }\n\n  args.push('-c:v', 'libx264', '-preset', 'ultrafast', outputName);\n\n  await ffmpeg.exec(args);\n\n  const rotatedData = await ffmpeg.readFile(outputName);\n  return new File(\n    [new Blob([rotatedData as any], { type: 'video/mp4' })],\n    `${input.name.replace(/\\.[^/.]+$/, '')}_rotated.mp4`,\n    { type: 'video/mp4' }\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/trim/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useCallback, useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { updateNumberField } from '@utils/string';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\nimport { debounce } from 'lodash';\nimport ToolVideoInput from '@components/input/ToolVideoInput';\nimport { useTranslation } from 'react-i18next';\n\nconst ffmpeg = new FFmpeg();\n\nconst initialValues = {\n  trimStart: 0,\n  trimEnd: 100\n};\n\nconst validationSchema = Yup.object({\n  trimStart: Yup.number().min(0, 'Start time must be positive'),\n  trimEnd: Yup.number().min(\n    Yup.ref('trimStart'),\n    'End time must be greater than start time'\n  )\n});\n\nexport default function TrimVideo({ title }: ToolComponentProps) {\n  const { t } = useTranslation('video');\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n\n  const compute = async (\n    optionsValues: typeof initialValues,\n    input: File | null\n  ) => {\n    if (!input) return;\n\n    const { trimStart, trimEnd } = optionsValues;\n\n    try {\n      if (!ffmpeg.loaded) {\n        await ffmpeg.load({\n          wasmURL:\n            'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n        });\n      }\n\n      const inputName = 'input.mp4';\n      const outputName = 'output.mp4';\n      // Load file into FFmpeg's virtual filesystem\n      await ffmpeg.writeFile(inputName, await fetchFile(input));\n      // Run FFmpeg command to trim video\n      await ffmpeg.exec([\n        '-i',\n        inputName,\n        '-ss',\n        trimStart.toString(),\n        '-to',\n        trimEnd.toString(),\n        '-c',\n        'copy',\n        outputName\n      ]);\n      // Retrieve the processed file\n      const trimmedData = await ffmpeg.readFile(outputName);\n      const trimmedBlob = new Blob([trimmedData as any], { type: 'video/mp4' });\n      const trimmedFile = new File(\n        [trimmedBlob],\n        `${input.name.replace(/\\.[^/.]+$/, '')}_trimmed.mp4`,\n        {\n          type: 'video/mp4'\n        }\n      );\n\n      setResult(trimmedFile);\n    } catch (error) {\n      console.error('Error trimming video:', error);\n    }\n  };\n  const debouncedCompute = useCallback(debounce(compute, 1000), []);\n  const getGroups: GetGroupsType<typeof initialValues> = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: t('trim.timestamps'),\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            onOwnChange={(value) =>\n              updateNumberField(value, 'trimStart', updateField)\n            }\n            value={values.trimStart}\n            label={t('trim.startTime')}\n            sx={{ mb: 2, backgroundColor: 'background.paper' }}\n          />\n          <TextFieldWithDesc\n            onOwnChange={(value) =>\n              updateNumberField(value, 'trimEnd', updateField)\n            }\n            value={values.trimEnd}\n            label={t('trim.endTime')}\n          />\n        </Box>\n      )\n    }\n  ];\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      renderCustomInput={({ trimStart, trimEnd }, setFieldValue) => {\n        return (\n          <ToolVideoInput\n            value={input}\n            onChange={setInput}\n            title={t('trim.inputTitle')}\n            showTrimControls={true}\n            onTrimChange={(trimStart, trimEnd) => {\n              setFieldValue('trimStart', trimStart);\n              setFieldValue('trimEnd', trimEnd);\n            }}\n            trimStart={trimStart}\n            trimEnd={trimEnd}\n          />\n        );\n      }}\n      resultComponent={\n        <ToolFileResult\n          title={t('trim.resultTitle')}\n          value={result}\n          extension={'mp4'}\n        />\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      compute={debouncedCompute}\n      setInput={setInput}\n      validationSchema={validationSchema}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/trim/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('video', {\n  path: 'trim',\n  icon: 'material-symbols:content-cut',\n  keywords: ['video', 'trim', 'cut', 'edit', 'time'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'video:trim.title',\n    description: 'video:trim.description',\n    shortDescription: 'video:trim.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/video/video-to-gif/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport * as Yup from 'yup';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport { GetGroupsType } from '@components/options/ToolOptions';\nimport TextFieldWithDesc from '@components/options/TextFieldWithDesc';\nimport { updateNumberField } from '@utils/string';\nimport { InitialValuesType } from './types';\nimport ToolVideoInput from '@components/input/ToolVideoInput';\nimport ToolFileResult from '@components/result/ToolFileResult';\nimport SimpleRadio from '@components/options/SimpleRadio';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile } from '@ffmpeg/util';\n\nconst initialValues: InitialValuesType = {\n  quality: 'mid',\n  fps: '10',\n  scale: '320:-1:flags=bicubic',\n  start: 0,\n  end: 100\n};\n\nconst validationSchema = Yup.object({\n  start: Yup.number().min(0, 'Start time must be positive'),\n  end: Yup.number().min(\n    Yup.ref('start'),\n    'End time must be greater than start time'\n  )\n});\n\nexport default function VideoToGif({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const [input, setInput] = useState<File | null>(null);\n  const [result, setResult] = useState<File | null>(null);\n  const [loading, setLoading] = useState(false);\n\n  const compute = (values: InitialValuesType, input: File | null) => {\n    if (!input) return;\n    const { fps, scale, start, end } = values;\n    let ffmpeg: FFmpeg | null = null;\n    let ffmpegLoaded = false;\n\n    const convertVideoToGif = async (\n      file: File,\n      fps: string,\n      scale: string,\n      start: number,\n      end: number\n    ): Promise<void> => {\n      setLoading(true);\n\n      if (!ffmpeg) {\n        ffmpeg = new FFmpeg();\n      }\n\n      if (!ffmpegLoaded) {\n        await ffmpeg.load({\n          wasmURL:\n            'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm'\n        });\n        ffmpegLoaded = true;\n      }\n\n      const fileName = file.name;\n      const outputName = 'output.gif';\n\n      try {\n        ffmpeg.writeFile(fileName, await fetchFile(file));\n\n        await ffmpeg.exec([\n          '-i',\n          fileName,\n          '-ss',\n          start.toString(),\n          '-to',\n          end.toString(),\n          '-vf',\n          `fps=${fps},scale=${scale},palettegen`,\n          'palette.png'\n        ]);\n\n        await ffmpeg.exec([\n          '-i',\n          fileName,\n          '-i',\n          'palette.png',\n          '-ss',\n          start.toString(),\n          '-to',\n          end.toString(),\n          '-filter_complex',\n          `fps=${fps},scale=${scale}[x];[x][1:v]paletteuse`,\n          outputName\n        ]);\n\n        const data = await ffmpeg.readFile(outputName);\n\n        const blob = new Blob([data as any], { type: 'image/gif' });\n        const convertedFile = new File([blob], outputName, {\n          type: 'image/gif'\n        });\n\n        await ffmpeg.deleteFile(fileName);\n        await ffmpeg.deleteFile(outputName);\n\n        setResult(convertedFile);\n      } catch (err) {\n        console.error(`Failed to convert video: ${err}`);\n        throw err;\n      } finally {\n        setLoading(false);\n      }\n    };\n\n    convertVideoToGif(input, fps, scale, start, end);\n  };\n\n  const getGroups: GetGroupsType<InitialValuesType> | null = ({\n    values,\n    updateField\n  }) => [\n    {\n      title: 'Set Quality',\n      component: (\n        <Box>\n          <SimpleRadio\n            title=\"Low\"\n            onClick={() => {\n              updateField('quality', 'low');\n              updateField('fps', '5');\n              updateField('scale', '240:-1:flags=bilinear');\n            }}\n            checked={values.quality === 'low'}\n          />\n          <SimpleRadio\n            title=\"Mid\"\n            onClick={() => {\n              updateField('quality', 'mid');\n              updateField('fps', '10');\n              updateField('scale', '320:-1:flags=bicubic');\n            }}\n            checked={values.quality === 'mid'}\n          />\n          <SimpleRadio\n            title=\"High\"\n            onClick={() => {\n              updateField('quality', 'high');\n              updateField('fps', '15');\n              updateField('scale', '480:-1:flags=lanczos');\n            }}\n            checked={values.quality === 'high'}\n          />\n          <SimpleRadio\n            title=\"Ultra\"\n            onClick={() => {\n              updateField('quality', 'ultra');\n              updateField('fps', '15');\n              updateField('scale', '640:-1:flags=lanczos');\n            }}\n            checked={values.quality === 'ultra'}\n          />\n        </Box>\n      )\n    },\n    {\n      title: 'Timestamps',\n      component: (\n        <Box>\n          <TextFieldWithDesc\n            onOwnChange={(value) =>\n              updateNumberField(value, 'start', updateField)\n            }\n            value={values.start}\n            label=\"Start Time\"\n            sx={{ mb: 2, backgroundColor: 'background.paper' }}\n          />\n          <TextFieldWithDesc\n            onOwnChange={(value) =>\n              updateNumberField(value, 'end', updateField)\n            }\n            value={values.end}\n            label=\"End Time\"\n          />\n        </Box>\n      )\n    }\n  ];\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      renderCustomInput={({ start, end }, setFieldValue) => {\n        return (\n          <ToolVideoInput\n            value={input}\n            onChange={setInput}\n            title={'Input Video'}\n            showTrimControls={true}\n            onTrimChange={(start, end) => {\n              setFieldValue('start', start);\n              setFieldValue('end', end);\n            }}\n            trimStart={start}\n            trimEnd={end}\n          />\n        );\n      }}\n      resultComponent={\n        loading ? (\n          <ToolFileResult\n            title=\"Converting to Gif\"\n            value={null}\n            loading={true}\n          />\n        ) : (\n          <ToolFileResult\n            title=\"Converted to Gif\"\n            value={result}\n            extension=\"gif\"\n          />\n        )\n      }\n      initialValues={initialValues}\n      getGroups={getGroups}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{ title: `What is a ${title}?`, description: longDescription }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/video/video-to-gif/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('video', {\n  path: 'video-to-gif',\n  icon: 'material-symbols:gif',\n  keywords: ['video', 'gif', 'convert', 'animated', 'image'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'video:videoToGif.title',\n    description: 'video:videoToGif.description',\n    shortDescription: 'video:videoToGif.shortDescription',\n    userTypes: ['generalUsers']\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/video/video-to-gif/types.ts",
    "content": "export type InitialValuesType = {\n  quality: 'mid' | 'high' | 'low' | 'ultra';\n  fps: string;\n  scale: string;\n  start: number;\n  end: number;\n};\n"
  },
  {
    "path": "src/pages/tools/xml/index.ts",
    "content": "import { tool as xmlXmlValidator } from './xml-validator/meta';\nimport { tool as xmlXmlBeautifier } from './xml-beautifier/meta';\n\nexport const xmlTools = [xmlXmlBeautifier, xmlXmlValidator];\n"
  },
  {
    "path": "src/pages/tools/xml/xml-beautifier/index.tsx",
    "content": "import React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { beautifyXml } from './service';\nimport { InitialValuesType } from './types';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Beautify XML',\n    description: 'Beautify a compact XML string for readability.',\n    sampleText: '<root><item>1</item><item>2</item></root>',\n    sampleResult: `<root>\\n  <item>1</item>\\n  <item>2</item>\\n</root>`,\n    sampleOptions: {}\n  }\n];\n\nexport default function XmlBeautifier({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('xml');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (_values: InitialValuesType, input: string) => {\n    setResult(beautifyXml(input, {}));\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput\n          title={t('xmlBeautifier.inputTitle')}\n          value={input}\n          onChange={setInput}\n        />\n      }\n      resultComponent={\n        <ToolTextResult\n          title={t('xmlBeautifier.resultTitle')}\n          value={result}\n          extension=\"xml\"\n        />\n      }\n      initialValues={initialValues}\n      exampleCards={exampleCards}\n      getGroups={null}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('xmlBeautifier.toolInfo.title', { title }),\n        description: longDescription\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/xml/xml-beautifier/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('xml', {\n  path: 'xml-beautifier',\n  icon: 'material-symbols:code',\n\n  keywords: ['xml', 'beautify', 'format', 'code', 'indent'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'xml:xmlBeautifier.title',\n    description: 'xml:xmlBeautifier.description',\n    shortDescription: 'xml:xmlBeautifier.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/xml/xml-beautifier/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { XMLParser, XMLBuilder, XMLValidator } from 'fast-xml-parser';\n\nexport function beautifyXml(\n  input: string,\n  _options: InitialValuesType\n): string {\n  const valid = XMLValidator.validate(input);\n  if (valid !== true) {\n    if (typeof valid === 'object' && valid.err) {\n      return `Invalid XML: ${valid.err.msg} (line ${valid.err.line}, col ${valid.err.col})`;\n    }\n    return 'Invalid XML';\n  }\n  try {\n    const parser = new XMLParser();\n    const obj = parser.parse(input);\n    const builder = new XMLBuilder({ format: true, indentBy: '  ' });\n    return builder.build(obj);\n  } catch (e: any) {\n    return `Invalid XML: ${e.message}`;\n  }\n}\n"
  },
  {
    "path": "src/pages/tools/xml/xml-beautifier/types.ts",
    "content": "export type InitialValuesType = {\n  // splitSeparator: string;\n};\n"
  },
  {
    "path": "src/pages/tools/xml/xml-beautifier/xml-beautifier.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { beautifyXml } from './service';\n\ndescribe('xml-beautifier', () => {\n  it('beautifies valid XML', () => {\n    const input = '<root><a>1</a><b>2</b></root>';\n    const result = beautifyXml(input, {});\n    expect(result).toContain('<root>');\n    expect(result).toContain('  <a>1</a>');\n    expect(result).toContain('  <b>2</b>');\n  });\n\n  it('returns error for invalid XML', () => {\n    const input = '<root><a>1</b></root>';\n    const result = beautifyXml(input, {});\n    expect(result).toMatch(/Invalid XML/i);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools/xml/xml-validator/index.tsx",
    "content": "import { Box } from '@mui/material';\nimport React, { useState } from 'react';\nimport ToolContent from '@components/ToolContent';\nimport { ToolComponentProps } from '@tools/defineTool';\nimport ToolTextInput from '@components/input/ToolTextInput';\nimport ToolTextResult from '@components/result/ToolTextResult';\nimport { CardExampleType } from '@components/examples/ToolExamples';\nimport { validateXml } from './service';\nimport { InitialValuesType } from './types';\nimport { useTranslation } from 'react-i18next';\n\nconst initialValues: InitialValuesType = {};\n\nconst exampleCards: CardExampleType<InitialValuesType>[] = [\n  {\n    title: 'Validate XML',\n    description: 'Check if an XML string is well-formed.',\n    sampleText: '<root><item>1</item><item>2</item></root>',\n    sampleResult: 'Valid XML',\n    sampleOptions: {}\n  },\n  {\n    title: 'Invalid XML',\n    description: 'Example of malformed XML.',\n    sampleText: '<root><item>1</item><item>2</root>',\n    sampleResult: 'Invalid XML: ...',\n    sampleOptions: {}\n  }\n];\n\nexport default function XmlValidator({\n  title,\n  longDescription\n}: ToolComponentProps) {\n  const { t } = useTranslation('xml');\n  const [input, setInput] = useState<string>('');\n  const [result, setResult] = useState<string>('');\n\n  const compute = (_values: InitialValuesType, input: string) => {\n    setResult(validateXml(input, {}));\n  };\n\n  return (\n    <ToolContent\n      title={title}\n      input={input}\n      inputComponent={\n        <ToolTextInput\n          value={input}\n          onChange={setInput}\n          placeholder={t('xmlValidator.placeholder')}\n        />\n      }\n      resultComponent={<ToolTextResult value={result} extension=\"txt\" />}\n      initialValues={initialValues}\n      exampleCards={exampleCards}\n      getGroups={null}\n      setInput={setInput}\n      compute={compute}\n      toolInfo={{\n        title: t('xmlValidator.toolInfo.title'),\n        description: t('xmlValidator.toolInfo.description')\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/pages/tools/xml/xml-validator/meta.ts",
    "content": "import { defineTool } from '@tools/defineTool';\nimport { lazy } from 'react';\n\nexport const tool = defineTool('xml', {\n  path: 'xml-validator',\n  icon: 'material-symbols:check-circle',\n  keywords: ['xml', 'validate', 'check', 'syntax', 'errors'],\n  component: lazy(() => import('./index')),\n  i18n: {\n    name: 'xml:xmlValidator.title',\n    description: 'xml:xmlValidator.description',\n    shortDescription: 'xml:xmlValidator.shortDescription'\n  }\n});\n"
  },
  {
    "path": "src/pages/tools/xml/xml-validator/service.ts",
    "content": "import { InitialValuesType } from './types';\nimport { XMLValidator } from 'fast-xml-parser';\n\nexport function validateXml(\n  input: string,\n  _options: InitialValuesType\n): string {\n  const result = XMLValidator.validate(input);\n  if (result === true) {\n    return 'Valid XML';\n  } else if (typeof result === 'object' && result.err) {\n    return `Invalid XML: ${result.err.msg} (line ${result.err.line}, col ${result.err.col})`;\n  } else {\n    return 'Invalid XML: Unknown error';\n  }\n}\n"
  },
  {
    "path": "src/pages/tools/xml/xml-validator/types.ts",
    "content": "export type InitialValuesType = {\n  // splitSeparator: string;\n};\n"
  },
  {
    "path": "src/pages/tools/xml/xml-validator/xml-validator.service.test.ts",
    "content": "import { expect, describe, it } from 'vitest';\nimport { validateXml } from './service';\n\ndescribe('xml-validator', () => {\n  it('returns Valid XML for well-formed XML', () => {\n    const input = '<root><a>1</a><b>2</b></root>';\n    const result = validateXml(input, {});\n    expect(result).toBe('Valid XML');\n  });\n\n  it('returns error for invalid XML', () => {\n    const input = '<root><a>1</b></root>';\n    const result = validateXml(input, {});\n    expect(result).toMatch(/Invalid XML/i);\n  });\n});\n"
  },
  {
    "path": "src/pages/tools-by-category/index.tsx",
    "content": "import {\n  Box,\n  Divider,\n  Stack,\n  styled,\n  TextField,\n  useTheme\n} from '@mui/material';\nimport Grid from '@mui/material/Grid';\nimport Typography from '@mui/material/Typography';\nimport { Link, useNavigate, useParams } from 'react-router-dom';\nimport { filterTools, getToolsByCategory } from '../../tools';\nimport Hero from 'components/Hero';\nimport {\n  getI18nNamespaceFromToolCategory,\n  getToolCategoryTitle\n} from '@utils/string';\nimport { Icon } from '@iconify/react';\nimport { categoriesColors } from 'config/uiConfig';\nimport React, { useEffect } from 'react';\nimport IconButton from '@mui/material/IconButton';\nimport ArrowBackIcon from '@mui/icons-material/ArrowBack';\nimport SearchIcon from '@mui/icons-material/Search';\nimport { Helmet } from 'react-helmet';\nimport UserTypeFilter from '@components/UserTypeFilter';\nimport { useTranslation } from 'react-i18next';\nimport { I18nNamespaces, validNamespaces } from '../../i18n';\nimport { useUserTypeFilter } from '../../providers/UserTypeFilterProvider';\n\nconst StyledLink = styled(Link)(({ theme }) => ({\n  '&:hover': {\n    color: theme.palette.mode === 'dark' ? 'white' : theme.palette.primary.light\n  }\n}));\n\nexport default function ToolsByCategory() {\n  const navigate = useNavigate();\n  const theme = useTheme();\n  const mainContentRef = React.useRef<HTMLDivElement>(null);\n  const { categoryName } = useParams();\n  const [searchTerm, setSearchTerm] = React.useState<string>('');\n  const { selectedUserTypes, setSelectedUserTypes } = useUserTypeFilter();\n  const { t } = useTranslation(validNamespaces);\n  const rawTitle = getToolCategoryTitle(categoryName as string, t);\n  // First get tools by category without filtering\n  const toolsByCategory = getToolsByCategory(selectedUserTypes, t).find(\n    ({ type }) => type === categoryName\n  );\n  const categoryDefinedTools = toolsByCategory?.tools ?? [];\n\n  const categoryTools = filterTools(\n    categoryDefinedTools,\n    searchTerm,\n    selectedUserTypes,\n    t\n  );\n\n  useEffect(() => {\n    if (mainContentRef.current) {\n      mainContentRef.current.scrollIntoView({ behavior: 'smooth' });\n    }\n  }, []);\n\n  return (\n    <Box sx={{ backgroundColor: 'background.default' }}>\n      <Helmet>\n        <title>{rawTitle}</title>\n      </Helmet>\n      <Box\n        padding={{ xs: 1, md: 3, lg: 5 }}\n        display={'flex'}\n        flexDirection={'column'}\n        alignItems={'center'}\n        justifyContent={'center'}\n        width={'100%'}\n      >\n        <Hero />\n      </Box>\n      <Divider sx={{ borderColor: theme.palette.primary.main }} />\n      <Box ref={mainContentRef} mt={3} ml={{ xs: 1, md: 2, lg: 3 }} padding={3}>\n        <Stack direction={'row'} justifyContent={'space-between'} spacing={2}>\n          <Stack direction={'row'} alignItems={'center'} spacing={1}>\n            <IconButton onClick={() => navigate('/')}>\n              <ArrowBackIcon color={'primary'} />\n            </IconButton>\n            <Typography fontSize={22} color={theme.palette.primary.main}>\n              {t('translation:toolLayout.allToolsTitle', { type: rawTitle })}\n            </Typography>\n          </Stack>\n          <TextField\n            placeholder={'Search'}\n            InputProps={{\n              endAdornment: <SearchIcon />,\n              sx: {\n                borderRadius: 4,\n                backgroundColor: 'background.paper',\n                maxWidth: 400\n              }\n            }}\n            onChange={(event) => setSearchTerm(event.target.value)}\n          />\n        </Stack>\n        <Box\n          width={'100%'}\n          display={'flex'}\n          alignItems={'center'}\n          justifyContent={'center'}\n          my={2}\n        >\n          <UserTypeFilter\n            userTypes={toolsByCategory?.userTypes ?? undefined}\n            selectedUserTypes={selectedUserTypes}\n            onUserTypesChange={setSelectedUserTypes}\n          />\n        </Box>\n        <Grid container spacing={2}>\n          {categoryTools.map((tool, index) => (\n            <Grid item xs={12} md={6} lg={4} key={tool.path}>\n              <Stack\n                sx={{\n                  backgroundColor: 'background.paper',\n                  boxShadow: `5px 4px 2px ${\n                    theme.palette.mode === 'dark' ? 'black' : '#E9E9ED'\n                  }`,\n                  cursor: 'pointer',\n                  height: '100%',\n                  '&:hover': {\n                    backgroundColor: theme.palette.background.hover\n                  }\n                }}\n                onClick={() => navigate('/' + tool.path)}\n                direction={'row'}\n                alignItems={'center'}\n                spacing={2}\n                padding={2}\n                border={`1px solid ${theme.palette.background.default}`}\n                borderRadius={2}\n              >\n                <Icon\n                  icon={tool.icon ?? 'ph:compass-tool-thin'}\n                  fontSize={'60px'}\n                  color={categoriesColors[index % categoriesColors.length]}\n                />\n                <Box>\n                  <StyledLink\n                    style={{\n                      fontSize: 20\n                    }}\n                    to={'/' + tool.path}\n                  >\n                    {/*@ts-ignore*/}\n                    {t(tool.name)}\n                  </StyledLink>\n                  <Typography sx={{ mt: 2 }}>\n                    {/*@ts-ignore*/}\n                    {t(tool.shortDescription)}\n                  </Typography>\n                </Box>\n              </Stack>\n            </Grid>\n          ))}\n        </Grid>\n      </Box>\n    </Box>\n  );\n}\n"
  },
  {
    "path": "src/providers/UserTypeFilterProvider.tsx",
    "content": "import React, {\n  createContext,\n  useContext,\n  useState,\n  useEffect,\n  ReactNode,\n  useMemo\n} from 'react';\nimport { UserType } from '@tools/defineTool';\n\ninterface UserTypeFilterContextType {\n  selectedUserTypes: UserType[];\n  setSelectedUserTypes: (userTypes: UserType[]) => void;\n}\n\nconst UserTypeFilterContext = createContext<UserTypeFilterContextType | null>(\n  null\n);\n\ninterface UserTypeFilterProviderProps {\n  children: ReactNode;\n}\n\nexport function UserTypeFilterProvider({\n  children\n}: UserTypeFilterProviderProps) {\n  const [selectedUserTypes, setSelectedUserTypes] = useState<UserType[]>(() => {\n    try {\n      const saved = localStorage.getItem('selectedUserTypes');\n      return saved ? JSON.parse(saved) : [];\n    } catch (error) {\n      console.error(\n        'Error loading selectedUserTypes from localStorage:',\n        error\n      );\n      return [];\n    }\n  });\n\n  useEffect(() => {\n    try {\n      localStorage.setItem(\n        'selectedUserTypes',\n        JSON.stringify(selectedUserTypes)\n      );\n    } catch (error) {\n      console.error('Error saving selectedUserTypes to localStorage:', error);\n    }\n  }, [selectedUserTypes]);\n\n  const contextValue = useMemo(\n    () => ({\n      selectedUserTypes,\n      setSelectedUserTypes\n    }),\n    [selectedUserTypes]\n  );\n\n  return (\n    <UserTypeFilterContext.Provider value={contextValue}>\n      {children}\n    </UserTypeFilterContext.Provider>\n  );\n}\n\nexport function useUserTypeFilter(): UserTypeFilterContextType {\n  const context = useContext(UserTypeFilterContext);\n\n  if (!context) {\n    throw new Error(\n      'useUserTypeFilter must be used within a UserTypeFilterProvider. ' +\n        'Make sure your component is wrapped with <UserTypeFilterProvider>.'\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "src/tools/defineTool.tsx",
    "content": "import ToolLayout from '../components/ToolLayout';\nimport React, { JSXElementConstructor, LazyExoticComponent } from 'react';\nimport { IconifyIcon } from '@iconify/react';\nimport { FullI18nKey, validNamespaces } from '../i18n';\nimport { useTranslation } from 'react-i18next';\n\nexport type UserType = 'generalUsers' | 'developers';\n\nexport interface ToolMeta {\n  path: string;\n  component: LazyExoticComponent<JSXElementConstructor<ToolComponentProps>>;\n  keywords: string[];\n  icon: IconifyIcon | string;\n  i18n: {\n    name: FullI18nKey;\n    description: FullI18nKey;\n    shortDescription: FullI18nKey;\n    longDescription?: FullI18nKey;\n    userTypes?: UserType[];\n  };\n}\n\nexport type ToolCategory =\n  | 'string'\n  | 'image-generic'\n  | 'png'\n  | 'number'\n  | 'gif'\n  | 'list'\n  | 'json'\n  | 'time'\n  | 'csv'\n  | 'video'\n  | 'pdf'\n  | 'audio'\n  | 'xml'\n  | 'converters';\n\nexport interface DefinedTool {\n  type: ToolCategory;\n  path: string;\n  name: FullI18nKey;\n  description: FullI18nKey;\n  shortDescription: FullI18nKey;\n  icon: IconifyIcon | string;\n  keywords: string[];\n  component: () => JSX.Element;\n  userTypes?: UserType[];\n}\n\nexport interface ToolComponentProps {\n  title: string;\n  longDescription?: string;\n}\n\nexport const defineTool = (\n  basePath: ToolCategory,\n  options: ToolMeta\n): DefinedTool => {\n  const { icon, path, keywords, component, i18n } = options;\n  const Component = component;\n  return {\n    type: basePath,\n    path: `${basePath}/${path}`,\n    name: i18n.name,\n    icon,\n    description: i18n.description,\n    shortDescription: i18n.shortDescription,\n    keywords,\n    userTypes: i18n.userTypes,\n    component: function ToolComponent() {\n      const { t } = useTranslation(validNamespaces);\n      return (\n        <ToolLayout\n          icon={icon}\n          type={basePath}\n          i18n={i18n}\n          fullPath={`${basePath}/${path}`}\n        >\n          <Component\n            title={t(i18n.name)}\n            longDescription={\n              i18n.longDescription ? t(i18n.longDescription) : undefined\n            }\n          />\n        </ToolLayout>\n      );\n    }\n  };\n};\n"
  },
  {
    "path": "src/tools/index.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport type { TFunction } from 'i18next';\nimport { filterTools } from './index';\nimport type { DefinedTool } from './defineTool';\nimport type { FullI18nKey, I18nNamespaces } from '../i18n';\n\nconst mergePdfTool = {\n  type: 'pdf',\n  path: 'pdf/merge-pdf',\n  name: 'pdf:mergePdf.title' as FullI18nKey,\n  description: 'pdf:mergePdf.description' as FullI18nKey,\n  shortDescription: 'pdf:mergePdf.shortDescription' as FullI18nKey,\n  icon: 'icon',\n  keywords: [\n    'pdf',\n    'merge',\n    'extract',\n    'pages',\n    'combine',\n    'document',\n    'join',\n    'append'\n  ],\n  component: (() => null) as unknown,\n  userTypes: ['generalUsers']\n} as unknown as DefinedTool;\n\nconst base64Tool = {\n  type: 'string',\n  path: 'string/base64',\n  name: 'string:base64.title' as FullI18nKey,\n  description: 'string:base64.description' as FullI18nKey,\n  shortDescription: 'string:base64.shortDescription' as FullI18nKey,\n  icon: 'icon',\n  keywords: ['b64'],\n  component: (() => null) as unknown,\n  userTypes: ['generalUsers', 'developers']\n} as unknown as DefinedTool;\n\nconst otherPdfTool = {\n  type: 'pdf',\n  path: 'pdf/other-pdf',\n  name: 'pdf:otherPdf.title' as FullI18nKey,\n  description: 'pdf:otherPdf.description' as FullI18nKey,\n  shortDescription: 'pdf:otherPdf.shortDescription' as FullI18nKey,\n  icon: 'icon',\n  keywords: [],\n  component: (() => null) as unknown,\n  userTypes: ['generalUsers']\n} as unknown as DefinedTool;\ntype NamespacedT = TFunction<I18nNamespaces[]>;\n\nconst enTranslations: Record<string, string> = {\n  'pdf:mergePdf.title': 'Merge PDF',\n  'pdf:mergePdf.description': 'Merge multiple PDF files into one document',\n  'pdf:mergePdf.shortDescription': 'Merge PDF files',\n  'pdf:otherPdf.title': 'Document helper',\n  'pdf:otherPdf.description': 'Helper for pdf tasks',\n  'pdf:otherPdf.shortDescription': 'PDF helper',\n  'string:base64.title': 'Base64 Encoder/Decoder',\n  'string:base64.description': 'Encode or decode Base64 text',\n  'string:base64.shortDescription': 'Convert text to and from Base64'\n};\n\nconst esTranslations: Record<string, string> = {\n  'pdf:mergePdf.title': 'Unir PDF',\n  'pdf:mergePdf.description': 'Unir varios archivos PDF en un solo documento',\n  'pdf:mergePdf.shortDescription': 'Unir archivos PDF'\n};\n\nconst makeT = (dict: Record<string, string>): NamespacedT =>\n  ((key: string) => dict[key] ?? key) as unknown as NamespacedT;\n\ndescribe('filterTools token-based search', () => {\n  const tools = [mergePdfTool, base64Tool, otherPdfTool];\n  const tEn = makeT(enTranslations);\n  const tEs = makeT(esTranslations);\n\n  it('returns all tools when query is empty or whitespace', () => {\n    expect(filterTools(tools, '', [], tEn)).toEqual(tools);\n    expect(filterTools(tools, '   ', [], tEn)).toEqual(tools);\n  });\n\n  it('matches tools regardless of word order and extra whitespace', () => {\n    const result1 = filterTools(tools, 'pdf merge', [], tEn);\n    const result2 = filterTools(tools, '   merge   pdf  ', [], tEn);\n\n    expect(result1).toContain(mergePdfTool);\n    expect(result2).toContain(mergePdfTool);\n  });\n\n  it('matches base64 tool with different queries and is case-insensitive', () => {\n    expect(filterTools(tools, 'Base64', [], tEn)).toContain(base64Tool);\n    expect(filterTools(tools, 'base64 encoder', [], tEn)).toContain(base64Tool);\n  });\n\n  it('matches tools using keyword-based English synonyms', () => {\n    const result = filterTools(tools, 'pdf join', [], tEn);\n    expect(result).toContain(mergePdfTool);\n  });\n\n  it('matches base64 tool with spaced and short-form variants', () => {\n    const resultBase64Spaced = filterTools(tools, 'base 64', [], tEn);\n    const resultB64 = filterTools(tools, 'b64', [], tEn);\n\n    expect(resultBase64Spaced).toContain(base64Tool);\n    expect(resultB64).toContain(base64Tool);\n  });\n\n  it('ignores trailing spaces', () => {\n    const result = filterTools(tools, 'pdf merge   ', [], tEn);\n    expect(result).toContain(mergePdfTool);\n  });\n\n  it('works with non-English localized strings', () => {\n    const result = filterTools(tools, 'unir pdf', [], tEs);\n    expect(result).toContain(mergePdfTool);\n  });\n\n  it('tolerates single-character typos in query tokens', () => {\n    const result = filterTools(tools, 'merhe pdf', [], tEn);\n    expect(result[0]).toBe(mergePdfTool);\n  });\n\n  it('ranks tools with title matches above description-only matches', () => {\n    const result = filterTools(tools, 'pdf', [], tEn);\n    expect(result[0]).toBe(mergePdfTool);\n  });\n});\n"
  },
  {
    "path": "src/tools/index.ts",
    "content": "import { stringTools } from '../pages/tools/string';\nimport { imageTools } from '../pages/tools/image';\nimport { DefinedTool, ToolCategory, UserType } from './defineTool';\nimport { capitalizeFirstLetter } from '@utils/string';\nimport { numberTools } from '../pages/tools/number';\nimport { videoTools } from '../pages/tools/video';\nimport { audioTools } from 'pages/tools/audio';\nimport { listTools } from '../pages/tools/list';\nimport { Entries } from 'type-fest';\nimport { jsonTools } from '../pages/tools/json';\nimport { csvTools } from '../pages/tools/csv';\nimport { timeTools } from '../pages/tools/time';\nimport { IconifyIcon } from '@iconify/react';\nimport { pdfTools } from '../pages/tools/pdf';\nimport { xmlTools } from '../pages/tools/xml';\nimport { convertersTools } from '../pages/tools/converters';\nimport { TFunction } from 'i18next';\nimport { FullI18nKey, I18nNamespaces } from '../i18n';\n\nconst toolCategoriesOrder: ToolCategory[] = [\n  'image-generic',\n  'pdf',\n  'string',\n  'video',\n  'time',\n  'audio',\n  'json',\n  'list',\n  'csv',\n  'number',\n  'png',\n  'xml',\n  'gif',\n  'converters'\n];\nexport const tools: DefinedTool[] = [\n  ...imageTools,\n  ...stringTools,\n  ...jsonTools,\n  ...pdfTools,\n  ...listTools,\n  ...csvTools,\n  ...videoTools,\n  ...numberTools,\n  ...timeTools,\n  ...audioTools,\n  ...xmlTools,\n  ...convertersTools\n];\nconst categoriesConfig: {\n  type: ToolCategory;\n  title: FullI18nKey;\n  value: FullI18nKey;\n  icon: IconifyIcon | string;\n}[] = [\n  {\n    type: 'string',\n    icon: 'solar:text-bold-duotone',\n    value: 'translation:categories.string.description',\n    title: 'translation:categories.string.title'\n  },\n  {\n    type: 'png',\n    icon: 'ph:file-png-thin',\n    value: 'translation:categories.png.description',\n    title: 'translation:categories.png.title'\n  },\n  {\n    type: 'number',\n    icon: 'lsicon:number-filled',\n    value: 'translation:categories.number.description',\n    title: 'translation:categories.number.title'\n  },\n  {\n    type: 'gif',\n    icon: 'material-symbols-light:gif-rounded',\n    value: 'translation:categories.gif.description',\n    title: 'translation:categories.gif.title'\n  },\n  {\n    type: 'list',\n    icon: 'solar:list-bold-duotone',\n    value: 'translation:categories.list.description',\n    title: 'translation:categories.list.title'\n  },\n  {\n    type: 'json',\n    icon: 'lets-icons:json-light',\n    value: 'translation:categories.json.description',\n    title: 'translation:categories.json.title'\n  },\n  {\n    type: 'csv',\n    icon: 'material-symbols-light:csv-outline',\n    value: 'translation:categories.csv.description',\n    title: 'translation:categories.csv.title'\n  },\n  {\n    type: 'video',\n    icon: 'lets-icons:video-light',\n    value: 'translation:categories.video.description',\n    title: 'translation:categories.video.title'\n  },\n  {\n    type: 'pdf',\n    icon: 'tabler:pdf',\n    value: 'translation:categories.pdf.description',\n    title: 'translation:categories.pdf.title'\n  },\n  {\n    type: 'time',\n    icon: 'fluent-mdl2:date-time',\n    value: 'translation:categories.time.description',\n    title: 'translation:categories.time.title'\n  },\n  {\n    type: 'image-generic',\n    icon: 'material-symbols-light:image-outline-rounded',\n    value: 'translation:categories.image-generic.description',\n    title: 'translation:categories.image-generic.title'\n  },\n  {\n    type: 'audio',\n    icon: 'ic:twotone-audiotrack',\n    value: 'translation:categories.audio.description',\n    title: 'translation:categories.audio.title'\n  },\n  {\n    type: 'xml',\n    icon: 'mdi-light:xml',\n    value: 'translation:categories.xml.description',\n    title: 'translation:categories.xml.title'\n  },\n  {\n    type: 'converters',\n    icon: 'streamline-plump:convert-pdf-1',\n    value: 'translation:categories.converters.description',\n    title: 'translation:categories.converters.title'\n  }\n];\nconst CATEGORIES_USER_TYPES_MAPPINGS: Partial<Record<ToolCategory, UserType>> =\n  {\n    xml: 'developers',\n    csv: 'developers',\n    json: 'developers',\n    gif: 'generalUsers',\n    png: 'generalUsers',\n    'image-generic': 'generalUsers',\n    video: 'generalUsers',\n    audio: 'generalUsers',\n    converters: 'generalUsers'\n  };\n// Filter tools by user types\nexport const filterToolsByUserTypes = (\n  tools: DefinedTool[],\n  userTypes: UserType[]\n): DefinedTool[] => {\n  if (userTypes.length === 0) return tools;\n\n  return tools.filter((tool) => {\n    if (CATEGORIES_USER_TYPES_MAPPINGS[tool.type]) {\n      return userTypes.includes(CATEGORIES_USER_TYPES_MAPPINGS[tool.type]!);\n    }\n    // If tool has no userTypes defined, show it to all users\n    if (!tool.userTypes || tool.userTypes.length === 0) return true;\n\n    // Check if tool has any of the selected user types\n    return tool.userTypes.some((userType) => userTypes.includes(userType));\n  });\n};\n\n/**\n * Returns the Levenshtein distance between two strings.\n * @param a - First string.\n * @param b - Second string.\n * @returns Minimum number of single-character edits (insert, delete, substitute).\n */\nconst levenshtein = (a: string, b: string): number => {\n  if (a === b) return 0;\n  const aLen = a.length;\n  const bLen = b.length;\n\n  if (aLen === 0) return bLen;\n  if (bLen === 0) return aLen;\n\n  const dp: number[][] = Array.from({ length: aLen + 1 }, () =>\n    Array<number>(bLen + 1).fill(0)\n  );\n\n  for (let i = 0; i <= aLen; i += 1) dp[i][0] = i;\n  for (let j = 0; j <= bLen; j += 1) dp[0][j] = j;\n\n  for (let i = 1; i <= aLen; i += 1) {\n    for (let j = 1; j <= bLen; j += 1) {\n      const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n\n      dp[i][j] = Math.min(\n        dp[i - 1][j] + 1,\n        dp[i][j - 1] + 1,\n        dp[i - 1][j - 1] + cost\n      );\n    }\n  }\n\n  return dp[aLen][bLen];\n};\n\ntype SearchField = {\n  text: string;\n  words: string[];\n  weight: number;\n};\n\nconst splitWords = (text: string): string[] =>\n  text.split(/[^a-z0-9]+/g).filter(Boolean);\n\n/**\n * Normalizes a string by converting it to lowercase and removing diacritics\n * (accent marks). Useful for locale-insensitive string comparison and search.\n *\n * @param text - The string to normalize\n * @returns The normalized string with lowercase letters and no diacritics\n */\nconst normalizeText = (text: string): string =>\n  text\n    .toLowerCase()\n    .normalize('NFKD')\n    .replace(/\\p{Diacritic}/gu, '')\n    .trim()\n    .replace(/\\s+/g, ' ');\n\nconst computeToolScore = (\n  tool: DefinedTool,\n  tokens: string[],\n  t: TFunction<I18nNamespaces[]>\n): number => {\n  const name = normalizeText(t(tool.name));\n  const shortDescription = normalizeText(t(tool.shortDescription));\n  const description = normalizeText(t(tool.description));\n\n  const fields: SearchField[] = [\n    { text: name, words: splitWords(name), weight: 5 },\n    { text: shortDescription, words: splitWords(shortDescription), weight: 3 },\n    { text: description, words: splitWords(description), weight: 2 },\n    ...(tool.keywords ?? []).map((kw) => {\n      const normalized = normalizeText(kw);\n      return {\n        text: normalized,\n        words: splitWords(normalized),\n        weight: 4\n      };\n    })\n  ];\n\n  let totalScore = 0;\n\n  for (const token of tokens) {\n    if (!token) continue;\n\n    let bestForToken = 0;\n\n    for (const field of fields) {\n      let fieldScore = 0;\n\n      if (field.text.includes(token)) {\n        // Base score for substring match\n        fieldScore = field.weight * 2;\n\n        if (field.words.includes(token)) {\n          // Boost whole-word matches\n          fieldScore += field.weight;\n        }\n      } else {\n        for (const word of field.words) {\n          // Quick length check before full distance calculation\n          if (Math.abs(word.length - token.length) > 1) continue;\n\n          const dist = levenshtein(token, word);\n          if (dist === 1) {\n            fieldScore = Math.max(fieldScore, field.weight);\n          }\n        }\n      }\n\n      if (fieldScore > bestForToken) {\n        bestForToken = fieldScore;\n      }\n    }\n    // If any token fails to match (exact or fuzzy), treat the tool as a non-match.\n    if (bestForToken === 0) return 0;\n\n    totalScore += bestForToken;\n  }\n\n  return totalScore;\n};\n\nexport const filterTools = (\n  tools: DefinedTool[],\n  query: string,\n  userTypes: UserType[] = [],\n  t: TFunction<I18nNamespaces[]>\n): DefinedTool[] => {\n  let filteredTools = tools;\n\n  // First filter by user types\n  if (userTypes.length > 0) {\n    filteredTools = filterToolsByUserTypes(tools, userTypes);\n  }\n\n  const normalizedQuery = normalizeText(query);\n\n  // If query is empty after normalization, return all tools (after user-type filtering)\n  if (!normalizedQuery) return filteredTools;\n\n  const rawTokens = normalizedQuery.split(' ').filter(Boolean);\n  if (rawTokens.length === 0) return filteredTools;\n\n  // Expand tokens with simple alpha+digit concatenation variants, e.g. \"base\" + \"64\" -> \"base64\".\n  // This, combined with per-tool `keywords`, allows us to support aliases like\n  // \"base 64\" / \"base64\" / \"b64\" without requiring every form in every keyword list.\n  const tokens: string[] = [...rawTokens];\n\n  for (let i = 0; i < rawTokens.length - 1; i += 1) {\n    const current = rawTokens[i];\n    const next = rawTokens[i + 1];\n\n    if (/^[a-zA-Z]+$/.test(current) && /^\\d+$/.test(next)) {\n      tokens.push(`${current}${next}`);\n    }\n  }\n\n  const scored = filteredTools\n    .map((tool) => ({\n      tool,\n      score: computeToolScore(tool, tokens, t)\n    }))\n    .filter(({ score }) => score > 0)\n    .sort((a, b) => {\n      if (b.score !== a.score) return b.score - a.score;\n\n      const aName = t(a.tool.name).toLowerCase();\n      const bName = t(b.tool.name).toLowerCase();\n\n      return aName.localeCompare(bName);\n    });\n\n  return scored.map(({ tool }) => tool);\n};\n\nexport const getToolsByCategory = (\n  userTypes: UserType[] = [],\n  t: TFunction<I18nNamespaces[]>\n): {\n  title: string;\n  rawTitle: string;\n  description: string;\n  icon: IconifyIcon | string;\n  type: ToolCategory;\n  example: { title: string; path: string };\n  tools: DefinedTool[];\n  userTypes: UserType[]; // <-- Add this line\n}[] => {\n  const groupedByType: Partial<Record<ToolCategory, DefinedTool[]>> =\n    Object.groupBy(tools, ({ type }) => type);\n\n  return (Object.entries(groupedByType) as Entries<typeof groupedByType>)\n    .map(([type, tools]) => {\n      const categoryConfig = categoriesConfig.find(\n        (config) => config.type === type\n      );\n\n      // Filter tools by user types if specified\n      const filteredTools =\n        userTypes.length > 0\n          ? filterToolsByUserTypes(tools ?? [], userTypes)\n          : tools ?? [];\n\n      // Aggregate unique userTypes from all tools in this category\n      const aggregatedUserTypes = Array.from(\n        new Set((filteredTools ?? []).flatMap((tool) => tool.userTypes ?? []))\n      );\n\n      return {\n        rawTitle: categoryConfig?.title\n          ? t(categoryConfig.title)\n          : capitalizeFirstLetter(type),\n        title: categoryConfig?.title\n          ? t(categoryConfig.title)\n          : `${capitalizeFirstLetter(type)} Tools`,\n        description: categoryConfig?.value ? t(categoryConfig.value) : '',\n        type,\n        icon: categoryConfig!.icon,\n        tools: filteredTools,\n        example:\n          filteredTools.length > 0\n            ? { title: filteredTools[0].name, path: filteredTools[0].path }\n            : { title: '', path: '' },\n        userTypes: aggregatedUserTypes // <-- Add this line\n      };\n    })\n    .filter((category) => category.tools.length > 0)\n    .filter((category) =>\n      userTypes.length > 0\n        ? [...category.userTypes, CATEGORIES_USER_TYPES_MAPPINGS[category.type]]\n            .filter(Boolean)\n            .some((categoryUserType) => userTypes.includes(categoryUserType!))\n        : true\n    ) // Only show categories with tools\n    .sort(\n      (a, b) =>\n        toolCategoriesOrder.indexOf(a.type) -\n        toolCategoriesOrder.indexOf(b.type)\n    );\n};\n"
  },
  {
    "path": "src/utils/array.ts",
    "content": "/**\n * Transpose a 2D array (matrix).\n * @param {any[][]} matrix - The 2D array to transpose.\n * @returns {any[][]} - The transposed 2D array.\n **/\n\nexport function transpose<T>(matrix: T[][]): any[][] {\n  return matrix[0].map((_, colIndex) => matrix.map((row) => row[colIndex]));\n}\n\n/**\n * Normalize and fill a 2D array to ensure all rows have the same length.\n * @param {any[][]} matrix - The 2D array to normalize and fill.\n * @param {any} fillValue - The value to fill in for missing elements.\n * @param {number} desiredLength - The target length of the array. if given take it as maxLength.\n * @returns {any[][]} - The normalized and filled 2D array.\n * **/\nexport function normalizeAndFill<T>(\n  matrix: T[][],\n  fillValue: T,\n  desiredLength?: number\n): T[][] {\n  const maxLength = !desiredLength\n    ? Math.max(...matrix.map((row) => row.length))\n    : desiredLength;\n  return matrix.map((row) => {\n    const filledRow = [...row];\n    while (filledRow.length < maxLength) {\n      filledRow.push(fillValue);\n    }\n    return filledRow;\n  });\n}\n"
  },
  {
    "path": "src/utils/bookmark.ts",
    "content": "const bookmarkedToolsKey = 'bookmarkedTools';\n\nexport function getBookmarkedToolPaths(): string[] {\n  return (\n    localStorage\n      .getItem(bookmarkedToolsKey)\n      ?.split(',')\n      ?.filter((path) => path) ?? []\n  );\n}\n\nexport function isBookmarked(toolPath: string): boolean {\n  return getBookmarkedToolPaths().some((path) => path === toolPath);\n}\n\nexport function toggleBookmarked(toolPath: string) {\n  if (isBookmarked(toolPath)) {\n    unbookmark(toolPath);\n  } else {\n    bookmark(toolPath);\n  }\n}\n\nfunction bookmark(toolPath: string) {\n  localStorage.setItem(\n    bookmarkedToolsKey,\n    [toolPath, ...getBookmarkedToolPaths()].join(',')\n  );\n}\n\nfunction unbookmark(toolPath: string) {\n  localStorage.setItem(\n    bookmarkedToolsKey,\n    getBookmarkedToolPaths()\n      .filter((path) => path !== toolPath)\n      .join(',')\n  );\n}\n"
  },
  {
    "path": "src/utils/color.ts",
    "content": "export function areColorsSimilar(\n  color1: [number, number, number],\n  color2: [number, number, number],\n  similarity: number\n): boolean {\n  const colorDistance = (\n    c1: [number, number, number],\n    c2: [number, number, number]\n  ) => {\n    return Math.sqrt(\n      Math.pow(c1[0] - c2[0], 2) +\n        Math.pow(c1[1] - c2[1], 2) +\n        Math.pow(c1[2] - c2[2], 2)\n    );\n  };\n  const maxColorDistance = Math.sqrt(\n    Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2)\n  );\n  const similarityThreshold = (similarity / 100) * maxColorDistance;\n\n  return colorDistance(color1, color2) <= similarityThreshold;\n}\n\nexport function convertHexToRGBA(color: string): number {\n  // Remove the leading '#' if present\n  if (color.startsWith('#')) {\n    color = color.slice(1);\n  }\n\n  // Convert the color to a number and add the alpha channel\n  const colorValue = parseInt(color, 16);\n  const alphaChannel = 0xff;\n  return (colorValue << 8) | alphaChannel;\n}\n"
  },
  {
    "path": "src/utils/csv.ts",
    "content": "/**\n * Splits a CSV line into string[], handling quoted string.\n * @param {string} input - The CSV input string.\n * @param {string} delimiter - The character used to split csvlines.\n * @param {string} quoteChar - The character used to quotes csv values.\n * @returns {string[][]} - The CSV line as a 1D array.\n */\nfunction splitCsvLine(\n  line: string,\n  delimiter: string = ',',\n  quoteChar: string = '\"'\n): string[] {\n  const result: string[] = [];\n  let current = '';\n  let inQuotes = false;\n\n  for (let i = 0; i < line.length; i++) {\n    const char = line[i];\n    const nextChar = line[i + 1];\n\n    if (char === quoteChar) {\n      if (inQuotes && nextChar === quoteChar) {\n        current += quoteChar;\n        i++; // Skip the escaped quote\n      } else {\n        inQuotes = !inQuotes;\n      }\n    } else if (char === delimiter && !inQuotes) {\n      result.push(current.trim());\n      current = '';\n    } else {\n      current += char;\n    }\n  }\n  result.push(current.trim());\n  return result;\n}\n\n/**\n * Splits a CSV string into rows, skipping any blank lines.\n * @param {string} input - The CSV input string.\n * @param {string} commentCharacter - The character used to denote comments.\n * @returns {string[][]} - The CSV rows as a 2D array.\n */\nexport function splitCsv(\n  input: string,\n  deleteComment: boolean,\n  commentCharacter: string,\n  deleteEmptyLines: boolean,\n  delimiter: string = ',',\n  quoteChar: string = '\"'\n): string[][] {\n  let rows = input\n    .split('\\n')\n    .map((row) => splitCsvLine(row, delimiter, quoteChar));\n\n  // Remove comments if deleteComment is true\n  if (deleteComment && commentCharacter) {\n    rows = rows.filter((row) => !row[0].trim().startsWith(commentCharacter));\n  }\n\n  // Remove empty lines if deleteEmptyLines is true\n  if (deleteEmptyLines) {\n    rows = rows.filter((row) => row.some((cell) => cell.trim() !== ''));\n  }\n\n  return rows;\n}\n\n/**\n * get the headers from  a CSV string .\n * @param {string} input - The CSV input string.\n * @param {string} csvSeparator - The character used to separate values in the CSV.\n * @param {string} quoteChar - The character used to quotes csv values.\n * @param {string} commentCharacter - The character used to denote comments.\n * @returns {string[]} - The CSV header as a 1D array.\n */\nexport function getCsvHeaders(\n  csvString: string,\n  csvSeparator: string = ',',\n  quoteChar: string = '\"',\n  commentCharacter?: string\n): string[] {\n  const lines = csvString.split('\\n');\n\n  for (const line of lines) {\n    const trimmed = line.trim();\n\n    if (\n      trimmed === '' ||\n      (commentCharacter && trimmed.startsWith(commentCharacter))\n    ) {\n      continue; // skip empty or commented lines\n    }\n\n    const headerLine = splitCsvLine(trimmed, csvSeparator, quoteChar);\n    return headerLine.map((h) => h.replace(/^\\uFEFF/, '').trim());\n  }\n\n  return [];\n}\n"
  },
  {
    "path": "src/utils/file.ts",
    "content": "/**\n * Returns the file extension\n *\n * @param {string} filename - The filename\n * @return {string} - the file extension\n */\nexport function getFileExtension(filename: string): string {\n  const lastDot = filename.lastIndexOf('.');\n  if (lastDot <= 0) return ''; // No extension\n  return filename.slice(lastDot + 1).toLowerCase();\n}\n"
  },
  {
    "path": "src/utils/gif.ts",
    "content": "import { GifBinary } from 'omggif';\n\nexport function gifBinaryToFile(\n  gifBinary: GifBinary,\n  fileName: string,\n  mimeType: string = 'image/gif'\n): File {\n  // Convert GifBinary to Uint8Array\n  const uint8Array = new Uint8Array(gifBinary.length);\n  for (let i = 0; i < gifBinary.length; i++) {\n    uint8Array[i] = gifBinary[i];\n  }\n\n  const blob = new Blob([uint8Array], { type: mimeType });\n\n  // Create File from Blob\n  return new File([blob], fileName, { type: mimeType });\n}\n"
  },
  {
    "path": "src/utils/index.ts",
    "content": "export function classNames(...classes: unknown[]): string {\n  return classes.filter(Boolean).join(' ');\n}\n"
  },
  {
    "path": "src/utils/json.ts",
    "content": "/**\n * Collects all unique keys from an array of row objects, preserving first-encountered order.\n * Handles sparse rows where different rows may have different keys.\n *\n * @param rows - Array of flattened row objects\n * @returns Array of unique header strings in insertion order\n *\n * @example\n * getJsonHeaders([{ a: '1' }, { a: '2', b: '3' }]) // → ['a', 'b']\n */\nexport function getJsonHeaders(rows: Record<string, string>[]): string[] {\n  return Array.from(\n    rows.reduce<Set<string>>((set, row) => {\n      Object.keys(row).forEach((key) => set.add(key));\n      return set;\n    }, new Set())\n  );\n}\n"
  },
  {
    "path": "src/utils/number.ts",
    "content": "export function formatNumber(\n  num: number | string,\n  fallback: number = 0\n): number {\n  if (!num) return fallback;\n  const result: number = Number(num);\n  if (!result) {\n    return fallback;\n  }\n  return result >= 0 ? result : fallback;\n}\n"
  },
  {
    "path": "src/utils/string.ts",
    "content": "import { UpdateField } from '@components/options/ToolOptions';\nimport { getToolsByCategory } from '@tools/index';\nimport { ToolCategory } from '@tools/defineTool';\nimport { I18nNamespaces, validNamespaces } from '../i18n';\nimport { TFunction } from 'i18next';\n\n// Here starting the shared values for string manipulation.\n\n/**\n * This map is used to replace special characters with their visual representations.\n * It is useful for displaying strings in a more readable format, especially in tools\n **/\n\nexport const specialCharMap: { [key: string]: string } = {\n  '': '␀',\n  ' ': '␣',\n  '\\n': '↲',\n  '\\t': '⇥',\n  '\\r': '␍',\n  '\\f': '␌',\n  '\\v': '␋'\n};\n\n// Here starting the utility functions for string manipulation.\n\nexport function capitalizeFirstLetter(string: string | undefined) {\n  if (!string) return '';\n  return string.charAt(0).toUpperCase() + string.slice(1);\n}\n\nexport function isNumber(number: any): boolean {\n  return !isNaN(parseFloat(number)) && isFinite(number);\n}\n\nexport const updateNumberField = <T>(\n  val: string,\n  key: keyof T,\n  updateField: UpdateField<T>\n) => {\n  if (val === '') {\n    // @ts-ignore\n    updateField(key, '');\n  } else if (isNumber(val)) {\n    // @ts-ignore\n    updateField(key, Number(val));\n  }\n};\n\nexport const replaceSpecialCharacters = (str: string) => {\n  return str\n    .replace(/\\\\\"/g, '\"')\n    .replace(/\\\\n/g, '\\n')\n    .replace(/\\\\t/g, '\\t')\n    .replace(/\\\\r/g, '\\r')\n    .replace(/\\\\b/g, '\\b')\n    .replace(/\\\\f/g, '\\f')\n    .replace(/\\\\v/g, '\\v');\n};\n\nexport function reverseString(input: string): string {\n  return input.split('').reverse().join('');\n}\n\n/**\n * Checks if the input string contains only digits.\n * @param input - The string to validate.\n * @returns True if the input contains only digits including float, false otherwise.\n */\nexport function containsOnlyDigits(input: string): boolean {\n  return /^\\d+(\\.\\d+)?$/.test(input.trim());\n}\n\n/**\n * unquote a string if properly quoted.\n * @param value - The string to unquote.\n * @param quoteCharacter - The character used for quoting (e.g., '\"', \"'\").\n * @returns The unquoted string if it was quoted, otherwise the original string.\n */\nexport function unquoteIfQuoted(value: string, quoteCharacter: string): string {\n  if (\n    quoteCharacter &&\n    value.startsWith(quoteCharacter) &&\n    value.endsWith(quoteCharacter)\n  ) {\n    return value.slice(1, -1); // Remove first and last character\n  }\n  return value;\n}\n\n/**\n * Count the occurence of items.\n * @param array - array get from user with a custom delimiter.\n * @param ignoreItemCase - boolean status to ignore the case i .\n * @returns Dict of Items count {[Item]: occcurence}.\n */\nexport function itemCounter(\n  array: string[],\n  ignoreItemCase: boolean\n): { [key: string]: number } {\n  const dict: { [key: string]: number } = {};\n  for (const item of array) {\n    let key = ignoreItemCase ? item.toLowerCase() : item;\n\n    if (key in specialCharMap) {\n      key = specialCharMap[key];\n    }\n\n    dict[key] = (dict[key] || 0) + 1;\n  }\n  return dict;\n}\n\nexport const getToolCategoryTitle = (\n  categoryName: string,\n  t: TFunction<I18nNamespaces[]>\n): string =>\n  getToolsByCategory([], t).find((category) => category.type === categoryName)!\n    .rawTitle;\n\n// Type guard to check if a value is a valid I18nNamespaces\nconst isValidI18nNamespace = (value: string): value is I18nNamespaces => {\n  return validNamespaces.includes(value as I18nNamespaces);\n};\n\nexport const getI18nNamespaceFromToolCategory = (\n  category: ToolCategory\n): I18nNamespaces => {\n  // Map image-related categories to 'image'\n  if (['png', 'image-generic'].includes(category)) {\n    return 'image';\n  } else if (['gif'].includes(category)) {\n    return 'video';\n  }\n  // Use type guard to check if category is a valid I18nNamespaces\n  if (isValidI18nNamespace(category)) {\n    return category;\n  }\n\n  return 'translation';\n};\n"
  },
  {
    "path": "src/utils/time.ts",
    "content": "type TimeValidationResult = {\n  isValid: boolean;\n  hours: number;\n  minutes: number;\n  seconds: number;\n};\n\n/**\n *  Validates human-readable time format (HH:MM or HH:MM:SS)\n * Supports either ':' or '.' as a separator, but not both\n * @param {string} input - string time format\n * * @returns {{\n *   isValid: boolean,  // true if the input is a valid time\n *   hours: number,     // parsed hours (0 or greater)\n *   minutes: number,   // parsed minutes (0-59)\n *   seconds: number    // parsed seconds (0-59, 0 if not provided)\n * }}\n */\nexport function humanTimeValidation(input: string): TimeValidationResult {\n  const result = { isValid: false, hours: 0, minutes: 0, seconds: 0 };\n\n  if (!input) return result;\n\n  input = input.trim();\n\n  // Operator use validation\n  // use of one between these two operators '.' or ':'\n\n  const hasColon = input.includes(':');\n  const hasDot = input.includes('.');\n\n  if (hasColon && hasDot) return result;\n\n  if (!hasColon && !hasDot) return result;\n\n  const separator = hasColon ? ':' : '.';\n\n  // Time parts validation\n\n  const parts = input.split(separator);\n\n  if (parts.length < 2 || parts.length > 3) return result;\n\n  const [h, m, s = '0'] = parts;\n\n  // every character should be a digit\n  if (![h, m, s].every((x) => /^\\d+$/.test(x))) return result;\n\n  const hours = parseInt(h);\n  const minutes = parseInt(m);\n  const seconds = parseInt(s);\n\n  if (minutes < 0 || minutes > 59) return result;\n  if (seconds < 0 || seconds > 59) return result;\n  if (hours < 0) return result;\n\n  return { isValid: true, hours, minutes, seconds };\n}\n"
  },
  {
    "path": "tailwind.config.mjs",
    "content": "/** @type {import('tailwindcss').Config} */\n\nexport default {\n  content: ['./src/**/*.{mjs,js,ts,jsx,tsx}'],\n  theme: {\n    extend: {}\n  },\n  plugins: []\n};\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"./src\",\n    \"target\": \"esnext\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    \"types\": [\"vite/client\", \"vitest/globals\", \"@testing-library/jest-dom\"],\n    \"paths\": {\n      \"@tools/*\": [\"./tools/*\"],\n      \"@assets/*\": [\"./assets/*\"],\n      \"@components/*\": [\"./components/*\"],\n      \"@utils/*\": [\"./utils/*\"]\n    }\n  },\n  \"include\": [\"src\", \"./@types\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "/// <reference types=\"vitest\" />\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react-swc';\nimport tsconfigPaths from 'vite-tsconfig-paths';\n\n// https://vitejs.dev/config https://vitest.dev/config\nexport default defineConfig({\n  plugins: [react(), tsconfigPaths()],\n  define: {\n    'process.env': {}\n  },\n  optimizeDeps: {\n    exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util']\n  },\n  test: {\n    globals: true,\n    environment: 'happy-dom',\n    setupFiles: '.vitest/setup',\n    include: ['**/*.test.{ts,tsx}']\n  },\n  worker: { format: 'es' }\n});\n"
  }
]