diff --git a/eslint.config.js b/eslint.config.js index 08c71d8f..c2fec1f9 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,13 +1,14 @@ -import eslint from "@eslint/js"; -import { flatConfigs as importFlatConfigs } from "eslint-plugin-import"; -import prettierRecommended from "eslint-plugin-prettier/recommended"; -import globals from "globals"; -import * as typescriptEslint from "typescript-eslint"; -import vitest from "@vitest/eslint-plugin"; -import jestDom from "eslint-plugin-jest-dom"; -import jsxA11y from "eslint-plugin-jsx-a11y"; -import reactPlugin from "eslint-plugin-react"; -import reactHooks from "eslint-plugin-react-hooks"; +import eslint from '@eslint/js'; +import { flatConfigs as importFlatConfigs } from 'eslint-plugin-import'; +import prettierRecommended from 'eslint-plugin-prettier/recommended'; +import globals from 'globals'; +import * as typescriptEslint from 'typescript-eslint'; +import vitest from '@vitest/eslint-plugin'; +import jestDom from 'eslint-plugin-jest-dom'; +import jsxA11y from 'eslint-plugin-jsx-a11y'; +import reactPlugin from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import i18next from 'eslint-plugin-i18next'; const flat = typescriptEslint.config( eslint.configs.recommended, @@ -17,7 +18,7 @@ const flat = typescriptEslint.config( importFlatConfigs.typescript, { settings: { - "import/resolver": { + 'import/resolver': { typescript: true, node: true, }, @@ -34,168 +35,168 @@ const flat = typescriptEslint.config( parserOptions: { projectService: true }, }, ignores: [ - "node_modules/", - "build/", - "dist/", - "coverage/", - ".eslintrc.js", - ".eslintrc.cjs", - "start.mjs", - "start.js", - "*.config.js", - "plopfile.js", + 'node_modules/', + 'build/', + 'dist/', + 'coverage/', + '.eslintrc.js', + '.eslintrc.cjs', + 'start.mjs', + 'start.js', + '*.config.js', + 'plopfile.js', ], }, { - files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], + files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], rules: { // TypeScript's `noFallthroughCasesInSwitch` option is more robust (#6906) - "default-case": "off", + 'default-case': 'off', // 'tsc' already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/291) - "no-dupe-class-members": "off", + 'no-dupe-class-members': 'off', // 'tsc' already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/477) - "no-undef": "off", + 'no-undef': 'off', // Add TypeScript specific rules (and turn off ESLint equivalents) - "@typescript-eslint/consistent-type-assertions": "warn", - "no-array-constructor": "off", - "@typescript-eslint/no-array-constructor": "warn", - "no-redeclare": "off", - "@typescript-eslint/no-redeclare": "warn", - "no-use-before-define": "off", - "@typescript-eslint/no-use-before-define": [ - "warn", + '@typescript-eslint/consistent-type-assertions': 'warn', + 'no-array-constructor': 'off', + '@typescript-eslint/no-array-constructor': 'warn', + 'no-redeclare': 'off', + '@typescript-eslint/no-redeclare': 'warn', + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': [ + 'warn', { functions: false, classes: false, variables: false, typedefs: false }, ], - "no-unused-expressions": "off", - "@typescript-eslint/no-unused-expressions": [ - "error", + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': [ + 'error', { allowShortCircuit: true, allowTernary: true, allowTaggedTemplates: true }, ], - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": [ - "warn", + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', { - argsIgnorePattern: "^_", - caughtErrorsIgnorePattern: "^_", - destructuredArrayIgnorePattern: "^_", - varsIgnorePattern: "^_", + argsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + varsIgnorePattern: '^_', ignoreRestSiblings: true, }, ], - "no-useless-constructor": "off", - "@typescript-eslint/no-useless-constructor": "warn", - "@typescript-eslint/array-type": "error", - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/no-empty-object-type": ["error", { allowInterfaces: "always" }], + 'no-useless-constructor': 'off', + '@typescript-eslint/no-useless-constructor': 'warn', + '@typescript-eslint/array-type': 'error', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'always' }], - "prefer-const": "warn", - "no-var": "error", - "array-callback-return": "warn", - "dot-location": ["warn", "property"], - eqeqeq: ["warn", "smart"], - "new-parens": "warn", - "no-caller": "warn", - "no-cond-assign": ["warn", "except-parens"], - "no-const-assign": "warn", - "no-control-regex": "warn", - "no-delete-var": "warn", - "no-dupe-args": "warn", - "no-dupe-keys": "warn", - "no-duplicate-case": "warn", - "no-empty-character-class": "warn", - "no-empty-pattern": "warn", - "no-eval": "warn", - "no-ex-assign": "warn", - "no-extend-native": "warn", - "no-extra-bind": "warn", - "no-extra-label": "warn", - "no-fallthrough": "warn", - "no-func-assign": "warn", - "no-implied-eval": "warn", - "no-invalid-regexp": "warn", - "no-iterator": "warn", - "no-label-var": "warn", - "no-labels": ["warn", { allowLoop: true, allowSwitch: false }], - "no-lone-blocks": "warn", - "no-loop-func": "warn", - "no-mixed-operators": [ - "warn", + 'prefer-const': 'warn', + 'no-var': 'error', + 'array-callback-return': 'warn', + 'dot-location': ['warn', 'property'], + eqeqeq: ['warn', 'smart'], + 'new-parens': 'warn', + 'no-caller': 'warn', + 'no-cond-assign': ['warn', 'except-parens'], + 'no-const-assign': 'warn', + 'no-control-regex': 'warn', + 'no-delete-var': 'warn', + 'no-dupe-args': 'warn', + 'no-dupe-keys': 'warn', + 'no-duplicate-case': 'warn', + 'no-empty-character-class': 'warn', + 'no-empty-pattern': 'warn', + 'no-eval': 'warn', + 'no-ex-assign': 'warn', + 'no-extend-native': 'warn', + 'no-extra-bind': 'warn', + 'no-extra-label': 'warn', + 'no-fallthrough': 'warn', + 'no-func-assign': 'warn', + 'no-implied-eval': 'warn', + 'no-invalid-regexp': 'warn', + 'no-iterator': 'warn', + 'no-label-var': 'warn', + 'no-labels': ['warn', { allowLoop: true, allowSwitch: false }], + 'no-lone-blocks': 'warn', + 'no-loop-func': 'warn', + 'no-mixed-operators': [ + 'warn', { groups: [ - ["&", "|", "^", "~", "<<", ">>", ">>>"], - ["==", "!=", "===", "!==", ">", ">=", "<", "<="], - ["&&", "||"], - ["in", "instanceof"], + ['&', '|', '^', '~', '<<', '>>', '>>>'], + ['==', '!=', '===', '!==', '>', '>=', '<', '<='], + ['&&', '||'], + ['in', 'instanceof'], ], allowSamePrecedence: false, }, ], - "no-multi-str": "warn", - "no-global-assign": "warn", - "no-unsafe-negation": "warn", - "no-new-func": "warn", - "no-new-object": "warn", - "no-new-symbol": "warn", - "no-new-wrappers": "warn", - "no-obj-calls": "warn", - "no-octal": "warn", - "no-octal-escape": "warn", - "no-regex-spaces": "warn", - "no-restricted-syntax": ["warn", "WithStatement"], - "no-script-url": "warn", - "no-self-assign": "warn", - "no-self-compare": "warn", - "no-sequences": "warn", - "no-shadow-restricted-names": "warn", - "no-sparse-arrays": "warn", - "no-template-curly-in-string": "warn", - "no-this-before-super": "warn", - "no-throw-literal": "warn", - "no-restricted-globals": "warn", - "no-unreachable": "warn", - "no-unused-labels": "warn", - "no-useless-computed-key": "warn", - "no-useless-concat": "warn", - "no-useless-escape": "warn", - "no-useless-rename": ["warn", { ignoreDestructuring: false, ignoreImport: false, ignoreExport: false }], - "no-with": "warn", - "no-whitespace-before-property": "warn", - "require-yield": "warn", - "rest-spread-spacing": ["warn", "never"], - strict: ["warn", "never"], - "unicode-bom": ["warn", "never"], - "use-isnan": "warn", - "valid-typeof": "warn", - "no-restricted-properties": [ - "error", + 'no-multi-str': 'warn', + 'no-global-assign': 'warn', + 'no-unsafe-negation': 'warn', + 'no-new-func': 'warn', + 'no-new-object': 'warn', + 'no-new-symbol': 'warn', + 'no-new-wrappers': 'warn', + 'no-obj-calls': 'warn', + 'no-octal': 'warn', + 'no-octal-escape': 'warn', + 'no-regex-spaces': 'warn', + 'no-restricted-syntax': ['warn', 'WithStatement'], + 'no-script-url': 'warn', + 'no-self-assign': 'warn', + 'no-self-compare': 'warn', + 'no-sequences': 'warn', + 'no-shadow-restricted-names': 'warn', + 'no-sparse-arrays': 'warn', + 'no-template-curly-in-string': 'warn', + 'no-this-before-super': 'warn', + 'no-throw-literal': 'warn', + 'no-restricted-globals': 'warn', + 'no-unreachable': 'warn', + 'no-unused-labels': 'warn', + 'no-useless-computed-key': 'warn', + 'no-useless-concat': 'warn', + 'no-useless-escape': 'warn', + 'no-useless-rename': ['warn', { ignoreDestructuring: false, ignoreImport: false, ignoreExport: false }], + 'no-with': 'warn', + 'no-whitespace-before-property': 'warn', + 'require-yield': 'warn', + 'rest-spread-spacing': ['warn', 'never'], + strict: ['warn', 'never'], + 'unicode-bom': ['warn', 'never'], + 'use-isnan': 'warn', + 'valid-typeof': 'warn', + 'no-restricted-properties': [ + 'error', { - object: "require", - property: "ensure", - message: "Please use import() instead", + object: 'require', + property: 'ensure', + message: 'Please use import() instead', }, { - object: "System", - property: "import", - message: "Please use import() instead", + object: 'System', + property: 'import', + message: 'Please use import() instead', }, ], - "getter-return": "warn", + 'getter-return': 'warn', // https://github.com/import-js/eslint-plugin-import - "import/no-unresolved": "off", - "import/no-named-as-default-member": "off", - "import/first": "error", - "import/no-amd": "error", - "import/no-anonymous-default-export": "warn", - "import/no-webpack-loader-syntax": "error", - "import/no-self-import": "error", + 'import/no-unresolved': 'off', + 'import/no-named-as-default-member': 'off', + 'import/first': 'error', + 'import/no-amd': 'error', + 'import/no-anonymous-default-export': 'warn', + 'import/no-webpack-loader-syntax': 'error', + 'import/no-self-import': 'error', }, }, { - files: ["**/*.spec.ts?(x)"], + files: ['**/*.spec.ts?(x)'], rules: { - "@typescript-eslint/no-explicit-any": "off", + '@typescript-eslint/no-explicit-any': 'off', }, }, ); @@ -203,54 +204,54 @@ const flat = typescriptEslint.config( const flatReact = [ { // https://github.com/yannickcr/eslint-plugin-react - files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], + files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], ...reactPlugin.configs.flat?.recommended, settings: { - react: { version: "detect" }, - linkComponents: [{ name: "Link", linkAttribute: "to" }], + react: { version: 'detect' }, + linkComponents: [{ name: 'Link', linkAttribute: 'to' }], }, rules: { ...reactPlugin.configs.flat?.recommended.rules, - "react/jsx-sort-props": ["warn", { callbacksLast: true, noSortAlphabetically: true, reservedFirst: true }], - "react/prop-types": "off", - "react/display-name": "off", - "react/self-closing-comp": ["error", { component: true, html: true }], + 'react/jsx-sort-props': ['warn', { callbacksLast: true, noSortAlphabetically: true, reservedFirst: true }], + 'react/prop-types': 'off', + 'react/display-name': 'off', + 'react/self-closing-comp': ['error', { component: true, html: true }], }, }, { // https://github.com/yannickcr/eslint-plugin-react - files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], - ...reactPlugin.configs.flat?.["jsx-runtime"], + files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], + ...reactPlugin.configs.flat?.['jsx-runtime'], }, { // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y - files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], + files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], ...jsxA11y.flatConfigs.recommended, rules: { ...jsxA11y.flatConfigs.recommended.rules, - "jsx-a11y/media-has-caption": "off", + 'jsx-a11y/media-has-caption': 'off', }, }, { // https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks - files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], + files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], plugins: { - "react-hooks": reactHooks, + 'react-hooks': reactHooks, }, rules: { ...reactHooks.configs.recommended.rules, - "react-hooks/rules-of-hooks": "error", + 'react-hooks/rules-of-hooks': 'error', }, }, { // https://github.com/vitest-dev/eslint-plugin-vitest - files: ["**/*.{test,spec}.{ts,tsx}"], + files: ['**/*.{test,spec}.{ts,tsx}'], // @ts-ignore ...vitest.configs.recommended, rules: { // @ts-ignore ...vitest.configs.recommended.rules, - "vitest/expect-expect": "off", + 'vitest/expect-expect': 'off', }, languageOptions: { globals: { @@ -261,8 +262,12 @@ const flatReact = [ }, { // https://github.com/testing-library/eslint-plugin-jest-dom - files: ["**/*.{test,spec}.{ts,tsx}"], - ...jestDom.configs["flat/recommended"], + files: ['**/*.{test,spec}.{ts,tsx}'], + ...jestDom.configs['flat/recommended'], + }, + { + ignores: ['**/*.cy.tsx'], + ...i18next.configs['flat/recommended'], }, ]; diff --git a/package-lock.json b/package-lock.json index b9120dae..32997e38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,7 @@ "cypress": "^14.1.0", "eslint-config-prettier": "^10.1.1", "eslint-import-resolver-typescript": "^4.1.1", + "eslint-plugin-i18next": "^6.1.3", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest-dom": "5.5.0", "eslint-plugin-jsx-a11y": "^6.10.2", @@ -9057,6 +9058,30 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-i18next": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-i18next/-/eslint-plugin-i18next-6.1.3.tgz", + "integrity": "sha512-z/h4oBRd9wI1ET60HqcLSU6XPeAh/EPOrBBTyCdkWeMoYrWAaUVA+DOQkWTiNIyCltG4NTmy62SQisVXxoXurw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lodash": "^4.17.21", + "requireindex": "~1.1.0" + }, + "engines": { + "node": ">=18.10.0" + } + }, + "node_modules/eslint-plugin-i18next/node_modules/requireindex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.1.0.tgz", + "integrity": "sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.5" + } + }, "node_modules/eslint-plugin-import": { "version": "2.32.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", diff --git a/package.json b/package.json index 924d72bc..115c82a6 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "cypress": "^14.1.0", "eslint-config-prettier": "^10.1.1", "eslint-import-resolver-typescript": "^4.1.1", + "eslint-plugin-i18next": "^6.1.3", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest-dom": "5.5.0", "eslint-plugin-jsx-a11y": "^6.10.2", diff --git a/public/locales/en.json b/public/locales/en.json index 66946c18..364b6741 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -81,6 +81,7 @@ "detailsLabel": "Details:" }, "CopyKubeconfigButton": { + "kubeconfigButton": "Kubeconfig", "copiedMessage": "Copied to Clipboard", "failedMessage": "Failed to copy, Error:", "menuDownload": "Download", @@ -97,7 +98,10 @@ "navigateHome": "Back to Homepage" }, "IntelligentBreadcrumbs": { - "homeLabel": "Home" + "homeLabel": "Home", + "projects": "Projects", + "workspaces": "Workspaces", + "mcps": "MCPs" }, "MCPContext": { "errorMessage": "An unknown error occurred" @@ -107,6 +111,7 @@ "subtitleMessage": "Looks like this page is not opened inside of the Hyperspace Portal. Contact admins for help." }, "ShellBar": { + "betaButton": "Beta", "betaButtonDescription": "This web app is currently in Beta, and may not be ready for productive use. We're actively improving the experience and would love your feedback — your input helps shape the future of the app!", "signOutButton": "Sign Out", "feedbackMessageLabel": "Message", diff --git a/src/components/ControlPlanes/CopyKubeconfigButton.tsx b/src/components/ControlPlanes/CopyKubeconfigButton.tsx index b3404d69..8000f1ec 100644 --- a/src/components/ControlPlanes/CopyKubeconfigButton.tsx +++ b/src/components/ControlPlanes/CopyKubeconfigButton.tsx @@ -26,7 +26,7 @@ export default function CopyKubeconfigButton() { return ( <>