diff --git a/.eslintrc.cjs b/.eslintrc.cjs index f5d33038..6d34b24d 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -20,7 +20,13 @@ module.exports = { rules: { "@typescript-eslint/no-var-requires": "off" // Disable this specific rule for CJS files } - } + }, + { + files: ['src/i18n/**/translations.ts'], + rules: { + "internal-rules/check-i18n-keys": "off" //Disabled so no warnings are presented everytime pre-commit is run + } + }, ], parser: "@typescript-eslint/parser", parserOptions: { @@ -32,7 +38,7 @@ module.exports = { jsx: true } }, - plugins: ["@typescript-eslint", "solid", "import"], + plugins: ["@typescript-eslint", "solid", "import", "internal-rules"], rules: { "@typescript-eslint/no-unused-vars": [ "warn", diff --git a/README.md b/README.md index bfabb343..e5722d71 100644 --- a/README.md +++ b/README.md @@ -118,3 +118,61 @@ openssl base64 < | tr -d '\n' | tee some_signing_key.j - `SIGNING_KEY` <- the data from `` 3. Change the `versionCode` and `versionName` on `app/build.gradle` 4. Commit and push. + +## Translating + +### Testing language keys + +To check what keys are missing from your desired language: + +``` +just i18n $lang +``` + +### Adding new languages or keys + +1. In `src/i18n/` locate your desired language folder or create one if one does not exist + + - When creating a new language dir ensure it follows the ISO 639 2-letter standard + +2. In this folder create a file called `translations.ts`, this is where the translation keys for your desired language will be located + +3. Populate your translation file with a translation object where all of the keys will be located + +If you want to add Japanese you will create a file `/src/i18n/jp/translations.ts` and populate it with keys like so: + +``` +export default { + common: { + continue: "続ける", + ... + } +} +``` + +(You should compare your translations against the English language as all other languages are not the master and are likely deprecated) + +4. Add your new translation file to the `/src/i18n/config.ts` so you can begin to see them in the app + +``` +import fa from "~/i18n/jp/translations.ts" + +export const resources: { + ... + jp: { + translations: jp + } +} +``` + +5. Add your language to the `Language` object in `/src/utils/languages.ts`. This will allow you to select the language via the language selector in the UI. If your desired language is set as your primary language in your browser it will be selected automatically + +``` +export const LANGUAGE_OPTIONS: Language[] = [ + { + value: "日本語", + shortName: "jp" + }, +``` + +6. That's it! You should now be able to see your translation keys populating the app in your desired language. When youre ready go ahead and open a PR to have you language merged for others! diff --git a/android/app/build.gradle b/android/app/build.gradle index 718777d3..37de3a2a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.mutinywallet.mutinywallet" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 50 - versionName "0.5.9" + versionCode 51 + versionName "0.5.10" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index d9398078..cb0797e5 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -360,7 +360,7 @@ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.5.9; + MARKETING_VERSION = 1.5.10; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.mutinywallet.mutiny; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -387,7 +387,7 @@ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.5.9; + MARKETING_VERSION = 1.5.10; PRODUCT_BUNDLE_IDENTIFIER = com.mutinywallet.mutiny; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist index 9374fb59..3423456a 100644 --- a/ios/App/App/Info.plist +++ b/ios/App/App/Info.plist @@ -25,7 +25,7 @@ LSRequiresIPhoneOS NSCameraUsageDescription - For scanning invoices + Mutiny uses the camera to scan QR codes of bitcoin addresses and lightning invoices. UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -50,8 +50,8 @@ UIViewControllerBasedStatusBarAppearance LSApplicationQueriesSchemes - - nostr+walletconnect - + + nostr+walletconnect + diff --git a/justfile b/justfile index 70615194..a91b17ae 100644 --- a/justfile +++ b/justfile @@ -6,6 +6,10 @@ dev: pre: pnpm run pre-commit +i18n LANG: + #!/usr/bin/env bash + pnpm eslint-path "./src/i18n/{{LANG}}/translations.ts" --rule "{internal-rules/check-i18n-keys: warn}" + local: pnpm install && pnpm link --global "@mutinywallet/mutiny-wasm" diff --git a/package.json b/package.json index 3f9510c8..376322c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mutiny-wallet", - "version": "0.5.9", + "version": "0.5.10", "license": "MIT", "packageManager": "pnpm@8.6.6", "scripts": { @@ -8,7 +8,9 @@ "build": "tsc && vite build", "check-types": "tsc --noemit", "eslint": "eslint src", - "pre-commit": "pnpm run eslint && pnpm run check-types && pnpm run format", + "eslint-path": "eslint", + "check-i18n-keys": "eslint src/i18n/**/translations.ts --rule \"{internal-rules/check-i18n-keys: warn}\"", + "pre-commit": "pnpm eslint && pnpm run check-types && pnpm run format", "format": "prettier --write \"{.,src/**,e2e/**}/*.{ts,tsx,js,jsx,json,css,scss,md}\"", "check-format": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,scss,md}\"" }, @@ -26,6 +28,7 @@ "eslint": "^8.52.0", "eslint-import-resolver-typescript": "2.7.1", "eslint-plugin-import": "2.27.5", + "eslint-plugin-internal-rules": "file:tools/internal-rules", "eslint-plugin-prettier": "4.2.1", "eslint-plugin-solid": "0.13.0", "postcss": "^8.4.31", @@ -55,7 +58,7 @@ "@kobalte/core": "^0.9.8", "@kobalte/tailwindcss": "^0.5.0", "@modular-forms/solid": "^0.18.1", - "@mutinywallet/mutiny-wasm": "0.5.9", + "@mutinywallet/mutiny-wasm": "0.5.10", "@mutinywallet/waila-wasm": "^0.2.6", "@solid-primitives/upload": "^0.0.111", "@solidjs/meta": "^0.29.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14598a6b..14b7f1fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,8 +54,8 @@ importers: specifier: ^0.18.1 version: 0.18.1(solid-js@1.8.5) '@mutinywallet/mutiny-wasm': - specifier: 0.5.9 - version: 0.5.9 + specifier: 0.5.10 + version: 0.5.10 '@mutinywallet/waila-wasm': specifier: ^0.2.6 version: 0.2.6 @@ -120,6 +120,9 @@ importers: eslint-plugin-import: specifier: 2.27.5 version: 2.27.5(@typescript-eslint/parser@6.9.1)(eslint-import-resolver-typescript@2.7.1)(eslint@8.52.0) + eslint-plugin-internal-rules: + specifier: file:tools/internal-rules + version: file:tools/internal-rules(eslint@8.52.0) eslint-plugin-prettier: specifier: 4.2.1 version: 4.2.1(eslint@8.52.0)(prettier@3.0.3) @@ -2570,8 +2573,8 @@ packages: solid-js: 1.8.5 dev: false - /@mutinywallet/mutiny-wasm@0.5.9: - resolution: {integrity: sha512-skSSpMprn/iA6Zsk092S1UVCkgjaCfXZXdvzVahFLDDS/89GtxyHtSsY64Oy3KFCULB6X+UfFp9nRFHtWA7sIw==} + /@mutinywallet/mutiny-wasm@0.5.10: + resolution: {integrity: sha512-wPyl0aUMThgAHZ47qMhpsoVK+o4UW77VfYTJ9cQtaO0xkt5FX0+9rQ7DMfA/8805f8O4xwItkRJqgy2N3FoeIw==} dev: false /@mutinywallet/waila-wasm@0.2.6: @@ -11565,6 +11568,11 @@ packages: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: true + /requireindex@1.2.0: + resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==} + engines: {node: '>=0.10.5'} + dev: true + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -13934,3 +13942,15 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + file:tools/internal-rules(eslint@8.52.0): + resolution: {directory: tools/internal-rules, type: directory} + id: file:tools/internal-rules + name: eslint-plugin-internal-rules + engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0} + peerDependencies: + eslint: '>=7' + dependencies: + eslint: 8.52.0 + requireindex: 1.2.0 + dev: true diff --git a/src/components/BetaWarningModal.tsx b/src/components/BetaWarningModal.tsx deleted file mode 100644 index 69bd9174..00000000 --- a/src/components/BetaWarningModal.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Dialog } from "@kobalte/core"; -import { ParentComponent } from "solid-js"; - -import { - DIALOG_CONTENT, - DIALOG_POSITIONER, - ExternalLink, - ModalCloseButton, - OVERLAY, - SmallHeader -} from "~/components"; -import { useI18n } from "~/i18n/context"; -import { useMegaStore } from "~/state/megaStore"; - -export function BetaWarningModal() { - const i18n = useI18n(); - return ( - -

{i18n.t("modals.beta_warning.be_careful")}

-

- - {i18n.t("modals.beta_warning.beta_link")} - -

-

- {i18n.t("modals.beta_warning.pretend_money")}{" "} - - {i18n.t("modals.beta_warning.signet_link")} - -

-
- ); -} - -const WarningModal: ParentComponent<{ - linkText: string; - title: string; -}> = (props) => { - const [state, actions] = useMegaStore(); - - function close() { - actions.setBetaWarned(); - } - - return ( - - - -
- -
- - {props.title} - - - - - -
{props.children}
-
-
-
-
-
-
- ); -}; diff --git a/src/components/ChooseLanguage.tsx b/src/components/ChooseLanguage.tsx index 360529da..f1bb7c6e 100644 --- a/src/components/ChooseLanguage.tsx +++ b/src/components/ChooseLanguage.tsx @@ -23,7 +23,7 @@ export function ChooseLanguage() { const [_chooseLanguageForm, { Form, Field }] = createForm({ initialValues: { - selectedLanguage: state.lang ?? "" + selectedLanguage: state.lang ?? i18n.language }, validate: (values) => { const errors: Record = {}; diff --git a/src/components/ContactEditor.tsx b/src/components/ContactEditor.tsx index 545cc1e1..666d0a69 100644 --- a/src/components/ContactEditor.tsx +++ b/src/components/ContactEditor.tsx @@ -68,7 +68,7 @@ export function ContactEditor(props: { previous: location.pathname }} > - Import Nostr Contacts + {i18n.t("contacts.link_to_nostr_sync")} diff --git a/src/components/ContactViewer.tsx b/src/components/ContactViewer.tsx index cf33afb4..55aae7a3 100644 --- a/src/components/ContactViewer.tsx +++ b/src/components/ContactViewer.tsx @@ -115,7 +115,7 @@ export function ContactViewer(props: { intent="red" onClick={() => setConfirmOpen(true)} > - Delete + {i18n.t("contacts.delete")} setConfirmOpen(false)} > - Are you sure you want to delete this contact? + {i18n.t("contacts.confirm_delete")} diff --git a/src/components/MutinyPlusCta.tsx b/src/components/MutinyPlusCta.tsx index de7c1f2b..58755745 100644 --- a/src/components/MutinyPlusCta.tsx +++ b/src/components/MutinyPlusCta.tsx @@ -29,7 +29,8 @@ export function MutinyPlusCta() { >
- Mutiny+ + {i18n.t("common.mutiny")} + + go
diff --git a/src/components/ReceiveWarnings.tsx b/src/components/ReceiveWarnings.tsx index aa4d2dd1..3bbf863a 100644 --- a/src/components/ReceiveWarnings.tsx +++ b/src/components/ReceiveWarnings.tsx @@ -50,7 +50,7 @@ export function ReceiveWarnings(props: { return undefined; }; - const betaWarning = () => { + const sillyAmountWarning = () => { const parsed = Number(props.amountSats); if (isNaN(parsed)) { return undefined; @@ -59,16 +59,13 @@ export function ReceiveWarnings(props: { if (parsed >= 2099999997690000) { // If over 21 million bitcoin, warn that too much return i18n.t("receive.amount_editable.more_than_21m"); - } else if (parsed >= 4000000) { - // If over 4 million sats, warn that it's a beta bro - return i18n.t("receive.amount_editable.too_big_for_beta"); } }; return ( - - {betaWarning()} + + {sillyAmountWarning()} diff --git a/src/components/index.ts b/src/components/index.ts index 287c4e7d..82f46a43 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -7,7 +7,6 @@ export * from "./ActivityItem"; export * from "./Amount"; export * from "./AmountEditable"; export * from "./BalanceBox"; -export * from "./BetaWarningModal"; export * from "./ChooseCurrency"; export * from "./ChooseLanguage"; export * from "./ContactEditor"; diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 1897837f..6a8352a5 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -4,6 +4,7 @@ import { use } from "i18next"; import LanguageDetector from "i18next-browser-languagedetector"; import en from "~/i18n/en/translations"; +import es from "~/i18n/es/translations"; import ko from "~/i18n/ko/translations"; import pt from "~/i18n/pt/translations"; @@ -11,6 +12,9 @@ export const resources = { en: { translations: en }, + es: { + translations: es + }, pt: { translations: pt }, diff --git a/src/i18n/en/translations.ts b/src/i18n/en/translations.ts index 4349a78b..f93b0fd8 100644 --- a/src/i18n/en/translations.ts +++ b/src/i18n/en/translations.ts @@ -1,6 +1,7 @@ export default { common: { title: "Mutiny Wallet", + mutiny: "Mutiny", nice: "Nice", home: "Home", e_sats: "eSATS", @@ -33,6 +34,8 @@ export default { create_contact: "Create contact", edit_contact: "Edit contact", save_contact: "Save contact", + delete: "Delete", + confirm_delete: "Are you sure you want to delete this contact?", payment_history: "Payment history", no_payments: "No payments yet with", edit: "Edit", @@ -47,7 +50,15 @@ export default { email_error: "That doesn't look like a lightning address", npub_error: "That doesn't look like a nostr npub", error_ln_address_missing: "New contacts need a lightning address", - npub: "Nostr Npub" + npub: "Nostr Npub", + link_to_nostr_sync: "Import Nostr Contacts" + }, + redeem: { + redeem_bitcoin: "Redeem Bitcoin", + lnurl_amount_message: + "Enter withdrawal amount between {{min}} and {{max}} sats", + lnurl_redeem_failed: "Withdrawal Failed", + lnurl_redeem_success: "Payment Received" }, receive: { receive_bitcoin: "Receive Bitcoin", @@ -89,8 +100,6 @@ export default { "A lightning setup fee might be deducted from the requested amount.", setup_fee_lightning: "A lightning setup fee will be charged if paid over lightning.", - too_big_for_beta: - "That's a lot of sats. You do know Mutiny Wallet is still in beta, yeah?", more_than_21m: "There are only 21 million bitcoin.", set_amount: "Set amount", max: "MAX", @@ -112,6 +121,13 @@ export default { what_for: "What's this for?" }, send: { + search: { + placeholder: "Name, address, invoice", + paste: "Paste", + contacts: "Contacts", + global_search: "Global search", + no_results: "No results found for" + }, sending: "Sending...", confirm_send: "Confirm Send", contact_placeholder: "Add the receiver for your records", @@ -233,7 +249,7 @@ export default { settings: { header: "Settings", support: "Learn how to support Mutiny", - beta_features: "BETA FEATURES", + experimental_features: "Experiments", debug_tools: "DEBUG TOOLS", danger_zone: "Danger zone", general: "General", @@ -394,6 +410,8 @@ export default { } }, encrypt: { + title: "Change Password", + caption: "Backup first to unlock encryption", header: "Encrypt your seed words", hot_wallet_warning: 'Mutiny is a "hot wallet" so it needs your seed word to operate, but you can optionally encrypt those words with a password.', @@ -474,7 +492,7 @@ export default { satisfaction: "Smug satisfaction", gifting: "Gifting", multi_device: "Multi-device access", - ios_beta_access: "iOS beta access", + ios_testflight: "iOS TestFlight access", more: "... and more to come", cta_description: "Enjoy early access to new features and premium functionality.", @@ -530,10 +548,12 @@ export default { save: "Save" }, nostr_contacts: { - title: "Nostr Contacts", + title: "Sync Nostr Contacts", npub_label: "Nostr npub", npub_required: "Npub can't be blank", - sync: "Sync" + sync: "Sync", + resync: "Resync", + remove: "Remove" }, manage_federations: { title: "Manage Federations", @@ -723,17 +743,6 @@ export default { not_available: "We don't do that yet", secure_your_funds: "Secure your funds" }, - beta_warning: { - title: "Warning: beta software", - beta_warning: - "We're so glad you're here. But we do want to warn you: Mutiny Wallet is in beta, and there are still bugs and rough edges.", - be_careful: - "Please be careful and don't put more money into Mutiny than you're willing to lose.", - beta_link: "Learn more about the beta", - pretend_money: - "If you want to use pretend money to test out Mutiny without risk,", - signet_link: "check out our Signet version." - }, more_info: { whats_with_the_fees: "What's with the fees?", self_custodial: diff --git a/src/i18n/es/translations.ts b/src/i18n/es/translations.ts new file mode 100644 index 00000000..34ab5dfa --- /dev/null +++ b/src/i18n/es/translations.ts @@ -0,0 +1,766 @@ +export default { + common: { + title: "Billetera Mutiny", + mutiny: "Mutiny", + nice: "Bien", + home: "Inicio", + e_sats: "eSATS", + e_sat: "eSAT", + sats: "SATS", + sat: "SAT", + fee: "Comisión", + send: "Enviar", + receive: "Recibir", + dangit: "¡Qué pena!", + back: "Atrás", + coming_soon: "(muy pronto)", + copy: "Copiar", + copied: "Copiado", + continue: "Continuar", + error_unimplemented: "Sin implementar", + why: "¿Por qué?", + private_tags: "Etiquetas privadas", + view_transaction: "Ver transacción", + view_payment_details: "Ver detalles del pago", + pending: "Pendiente", + error_safe_mode: + "Mutiny se está ejecutando en modo seguro. Lightning esta deshabilitado.", + self_hosted: "Auto-hospedado" + }, + contacts: { + new: "nuevo", + add_contact: "Añadir Contacto", + new_contact: "Nuevo Contacto", + create_contact: "Crear contacto", + edit_contact: "Editar contacto", + save_contact: "Guardar contacto", + delete: "Elminar", + confirm_delete: "¿Está seguro de querer eliminar este contacto?", + payment_history: "Historial de pagos", + no_payments: "Todavía no hay pagos con", + edit: "Editar", + pay: "Pagar", + name: "Nombre", + ln_address: "Dirección Lightning", + placeholder: "Satoshi", + lightning_address: "Dirección Lightning", + unimplemented: "Sin implementar", + not_available: "No hacemos eso todavía", + error_name: "Necesitamos por lo menos un nombre", + email_error: "Eso no parece una dirección lightning", + npub_error: "Eso no parece un npub de nostr", + error_ln_address_missing: + "Los contactos nuevos necesitan una dirección lightning", + npub: "Npub Nostr", + link_to_nostr_sync: "Importar Contactos de Nostr" + }, + receive: { + receive_bitcoin: "Recibir Bitcoin", + edit: "Editar", + checking: "Verificando", + choose_format: "Escoja formato", + payment_received: "Pago Recibido", + payment_initiated: "Pago Iniciado", + receive_add_the_sender: "Agregue el remitente para sus registros", + keep_mutiny_open: "Deje Mutiny abierto para completar el pago.", + choose_payment_format: "Escoja el formato de pago", + unified_label: "Unificado", + unified_caption: + "Combina una dirección de bitcoin y una factura lightning. El remitente escoge el método de pago.", + lightning_label: "Factura Lightning", + lightning_caption: + "Ideal para transacciones pequeñas. Usualmente comisiones más bajas que en-cadena.", + onchain_label: "Dirección bitcoin", + onchain_caption: + "En-cadena, tal como lo hizo Satoshi. Ideal para transacciones muy grandes.", + unified_setup_fee: + "Una comisión de instalación de lightning por {{amount}} SATS se cobrará si pagado a través de lightning.", + lightning_setup_fee: + "Una comisión de instalación de lightning por {{amount}} SATS se cobrará para esta recepción.", + amount: "Monto", + fee: "+ Comisión", + total: "Total", + spendable: "Gastable", + channel_size: "Tamaño canal", + channel_reserve: "- Reserva canal", + error_under_min_lightning: + "En-cadena por defecto. El monto es demasiado pequeño para su recepción en Lightning.", + error_creating_unified: + "En-cadena por defecto. Also salió mal al crear la dirección unificada", + error_creating_address: + "Also salió mal al crear la dirección en-cadena", + amount_editable: { + receive_too_small: + "Una comisión de instalación de lightning puede ser deducida del monto solicitado.", + setup_fee_lightning: + "Se cargará una comisión de instalación de lightning si se paga por lightning.", + more_than_21m: "Hay solo 21 millones bitcoins.", + set_amount: "Establecer monto", + max: "MAX", + fix_amounts: { + ten_k: "10k", + one_hundred_k: "100k", + one_million: "1m" + }, + del: "BORR", + balance: "Balance" + }, + integrated_qr: { + onchain: "En-cadena", + lightning: "Lightning", + unified: "Unificado", + gift: "Regalo Lightning" + }, + remember_choice: "Recordar my selección para la próxima vez", + what_for: "¿Para qué es esto?" + }, + send: { + search: { + placeholder: "Nombre, dirección, factura", + paste: "Pegar", + contacts: "Contactos", + global_search: "Búsqueda global", + no_results: "No hay resultados para" + }, + sending: "Enviando...", + confirm_send: "Confirmar Envío", + contact_placeholder: "Aggregar el recipiente para su registro", + start_over: "Comenzar de Nuevo", + send_bitcoin: "Enviar Bitcoin", + paste: "Pegar", + scan_qr: "Escanear QR", + payment_initiated: "Pago Iniciado", + payment_sent: "Pago Enviado", + destination: "Destino", + no_payment_info: "No hay información de pago", + progress_bar: { + of: "de", + sats_sent: "sats enviados" + }, + what_for: "¿Para qué es esto?", + zap_note: "Zapear nota", + error_low_balance: + "No tenemos saldo suficiente para pagar el monto indicada.", + error_invoice_match: + "Monto solicitado, {{amount}} SATS, no es igual al monto establecido.", + error_channel_reserves: "No hay suficientes fondos disponibles.", + error_address: "Dirección Lightning Inválida", + error_channel_reserves_explained: + "Una porción del balance de su canal es reservada para comisiones. Intente enviar un monto más pequeño o agregue fondos.", + error_clipboard: "Portapapeles no soportado", + error_keysend: "Keysend falló", + error_LNURL: "Pago LNURL falló", + error_expired: "La factura está expirada", + payjoin_send: + "Esto es un payjoin! El Mutiny continuará hasta que la privacidad mejore", + payment_pending: "Pago pendiente", + payment_pending_description: + "Se esta tomando un tiempo, pero es posible que este pago todavía se realice. Por favor verifique 'Actividad' para el estado actual.", + hodl_invoice_warning: + "Esta es una factura hodl. Los pagos a facturas hodl pueden causar cierres forzosos del canal, lo que resulta en comisiones altas en-cadena ¡Pague bajo su propio riesgo!", + private: "Privado", + anonzap: "Zap Anon" + }, + feedback: { + header: "¡Denos retroalimentación!", + received: "¡Retroalimentacion recibida!", + thanks: "Gracias por decirnos lo que está sucediendo.", + more: "¿Tiene algo más que decir?", + tracking: + "Mutiny no rastrea n espía su comportamiento, por lo que su retroalimentación es increíblemente útil.", + github: "No responderemos a esta retroalimentación. Si desea soporte por favor", + create_issue: "cree una issue en GitHub.", + link: "¿Retroalimentación?", + feedback_placeholder: + "Bugs, solicitud de funcionalidad, retroalimentación, etc.", + info_label: "Incluir información de contacto", + info_caption: "Si necesita que le demos seguimiento a este problema", + email: "Correo electrónico", + email_caption: "Direcciones desechables son bienvenidas", + nostr: "Nostr", + nostr_caption: "Su npub más fresco", + nostr_label: "npub de Nostr o NIP-05", + send_feedback: "Enviar Retroalimentación", + invalid_feedback: "¡Por favor diga algo!", + need_contact: "Necesitamos alguna forma de contactarlo", + invalid_email: "Eso no me parece una dirección de correo electrónico", + error: "Error enviando retroalimentación {{error}}", + try_again: "Por favor intente de nuevo más tarde." + }, + activity: { + title: "Actividad", + mutiny: "Mutiny", + wallet: "Billetera", + nostr: "Nostr", + view_all: "Ver todo", + receive_some_sats_to_get_started: "Reciba algunos sats para comenzar", + channel_open: "Canal Abierto", + channel_close: "Canal Cerrado", + unknown: "Desconocido", + import_contacts: + "Importe sus contactos desde nostr para ver a quién están zapeando.", + coming_soon: "Muy pronto", + private: "Privado", + anonymous: "Anónimo", + from: "De:", + transaction_details: { + lightning_receive: "Recibido via Lightning", + lightning_send: "Enviado via Lightning", + channel_open: "Canal abierto", + channel_close: "Canal cerrado", + onchain_receive: "Recibido en-cadena", + onchain_send: "Enviado en-cadena", + paid: "Pagado", + unpaid: "Sin pagar", + status: "Estado", + date: "Fecha", + tagged_to: "Etiquetado a", + description: "Descripción", + fee: "Comisión", + onchain_fee: "Comisión En-cadena", + invoice: "Factura", + payment_hash: "Hash de Pago", + payment_preimage: "Preimagen", + txid: "Txid", + total: "Monto Solicitado", + balance: "Balance", + reserve: "Reserva", + peer: "Par", + channel_id: "ID Canal", + reason: "Razón", + confirmed: "Confirmado", + unconfirmed: "Sin Confirmar", + sweep_delay: + "Los fondos pueden tomar algunos días en regresar a la billetera", + no_details: + "No se encontraron detalles del canal, lo que significa que probablemente el canal ha sido cerrado.", + back_home: "regresar a inicio" + } + }, + scanner: { + paste: "Pegar Algo", + cancel: "Cancelar" + }, + settings: { + header: "Ajustes", + support: "Entérese como apoyar a Mutiny", + experimental_features: "Experimentos", + debug_tools: "HERRAMIENTAS DE DEPURACIÓN", + danger_zone: "Zona de peligro", + general: "General", + version: "Versión:", + admin: { + title: "Página de Administración", + caption: + "Nuestras herramientas internas de depuración ¡Úselas sabiamente!", + header: "Herramientas Secretas de Depuración", + warning_one: + "Si sabe lo que está haciendo está en el lugar adecuado.", + warning_two: + "Estas son herramientas internas que usamos para depurar y probar la aplicación ¡Por favor tenga cuidado!", + kitchen_sink: { + disconnect: "Desconectar", + peers: "Pares", + no_peers: "No hay pares", + refresh_peers: "Refrescar Pares", + connect_peer: "Conectar a Par", + expect_a_value: "Esperando un valor...", + connect: "Conectar", + close_channel: "Cerrar Canal", + force_close: "Forzar cierre de canal", + abandon_channel: "Abandonar Canal", + confirm_close_channel: + "¿Está seguro de querer cerrar este canal?", + confirm_force_close: + "¿Está seguro de querer forzar el cierre de este canal? Sus fondos tardarán unos días en ser redimidos en la cadena.", + confirm_abandon_channel: + "¿Está seguro de querer abandonar este canal? Típicamente haga esto solo si la transacción de apertura no se confirma nunca. De lo contrario, perderá fondos.", + channels: "Canales", + no_channels: "No hay Canales", + refresh_channels: "Refrescar Canales", + pubkey: "Pubkey", + amount: "Monto", + open_channel: "Abrir Canal", + nodes: "Nodos", + no_nodes: "No hay nodos", + enable_zaps_to_hodl: "¿Habilitar zaps a facturas hodl?", + zaps_to_hodl_desc: + "Zaps a facturas hodl pueden resultar en el cierre forzoso de canales, lo que resulta en altas comisiones en-cadena ¡Use bajo su propio riesgo!", + zaps_to_hodl_enable: "Habilitar zaps hodl", + zaps_to_hodl_disable: "Deshabilitar zaps hodl" + } + }, + backup: { + title: "Respaldo", + secure_funds: "Aseguremos estos fondos.", + twelve_words_tip: + "Le mostraremos 12 palabras. Usted escribe esas 12 palabras.", + warning_one: + "Si limpia el historial de su navegador, o pierde su dispositivo, estas 12 palabras son la única manera de restaurar su billetera.", + warning_two: "Mutiny es auto-custodial. Todo depende de usted...", + confirm: "Escribí las palabras", + responsibility: "Entiendo que mis fondos son mi responsabilidad", + liar: "No estoy mintiendo solo para terminar con esto", + seed_words: { + reveal: "TOQUE PARA REVELAR LAS PALABRAS SEMILLA", + hide: "ESCONDER", + copy: "Peligrosamente Copiar al Portapapeles", + copied: "Copiado!" + } + }, + channels: { + title: "Canales Lightning", + outbound: "Saliente", + inbound: "Entrante", + reserve: "Reserva", + have_channels: "Tiene", + have_channels_one: "canal lightning.", + have_channels_many: "canales lightning.", + inbound_outbound_tip: + "Saliente es el monto de dinero que puede gastar en lightning. Entrante es el monto que puede recibir sin incurrir en una comisión de servicio de lightning.", + reserve_tip: + "Alrededor del 1% del balance de su canal está reservado en lightning para comisiones. Reservas adicionales son requeridas para canales que abrió usando swap.", + no_channels: + "Parece que todavía no tiene ningún canal. Para empezar, reciba algunos sats port lightning, o haga un swap para mover fondos en-cadena hacia un canal ¡Manos a la obra!", + close_channel: "Cerrar", + online_channels: "Canales en Línea", + offline_channels: "Canales Fuera de Línea", + close_channel_confirm: + "Cerrar este canal moverá el saldo en-cadena e incurrirá en una comisión en-cadena." + }, + connections: { + title: "Conexiones Billetera", + error_name: "El nombre no puede estar vacío", + error_connection: "Fallo al crear Conexión Billetera", + error_budget_zero: "El presupuesto debe ser mayor a cero", + add_connection: "Agregar Conexión", + manage_connections: "Manejar Conexiones", + manage_gifts: "Manejar Regalos", + delete_connection: "Eliminar", + new_connection: "Nueva Conexión", + edit_connection: "Editar Conexión", + new_connection_label: "Nombre", + new_connection_placeholder: "Mi cliente nostr favorito...", + create_connection: "Crear Conexión", + save_connection: "Guardar Cambios", + edit_budget: "Editar Presupuesto", + open_app: "Abrir Aplicación", + open_in_nostr_client: "Abrir en Cliente Nostr", + open_in_primal: "Abrir en Primal", + nostr_client_not_found: "Cliente nostr no encontrado", + client_not_found_description: + "Instale un cliente nostr como Primal, Amethyst, o Damus para abrir este enlace.", + relay: "Relé", + authorize: + "Autoriza servicios externos para solicitar pagos desde su billetera. Combina muy bien con clientes Nostr.", + pending_nwc: { + title: "Solicitudes Pendientes", + approve_all: "Aprovar Todo", + deny_all: "Denegar Todo" + }, + careful: + "¡Tenga cuidado dónde comparte esta conexión! Solicitudes dentro del presupuesto serán pagadas automáticamente.", + spent: "Gastado", + remaining: "Restante", + confirm_delete: "¿Está seguro de querer eliminar esta conexión?", + budget: "Presupuesto", + resets_every: "Se restablece cada", + resubscribe_date: "Suscribirse de nuevo en" + }, + emergency_kit: { + title: "Kit de Emergencia", + caption: "Diagnostique y resuelva problemas con su billetera.", + emergency_tip: + "Si su billetera parece dañada, aquí hay algunas herramientas para tratar depurarla y repararla.", + questions: + "Si tiene preguntas acerca de qué hacen estos botones, por favor", + link: "contáctenos para recibir soporte.", + import_export: { + title: "Exportar estado de la billetera", + error_password: "Contraseña requerida", + error_read_file: "Error al leer archivo", + error_no_text: "No se encontró texto en el archivo", + tip: "Puede exportar todo el estado de su Billetera Mutiny a un archivo e importarlo a otro navegador ¡Usualmente funciona!", + caveat_header: "Advertencias importantes:", + caveat: "después de exportar no realice ninguna operación en el navegador original. Si lo hace, tendrá que exportar de nuevo. Después de una importación exitosa, una buena práctica es borrar el estado del navegador original solo para asegurarse de no crear conflictos.", + save_state: "Guardar Estado a Archivo", + import_state: "Importar Estado desde Archivo", + confirm_replace: "¿Desea reemplazar su estado con", + password: "Ingrese su contraseña para descifrar", + decrypt_wallet: "Descifrar Billetera" + }, + logs: { + title: "Descargar logs de depuración", + something_screwy: + "Algo raro está sucediendo? ¡Verifique los logs!", + download_logs: "Descargar logs", + password: "Ingrese su contraseña para cifrar", + confirm_password_label: "Confirme la Contraseña" + }, + delete_everything: { + delete: "Eliminar Todo", + confirm: + "Esto eliminará el estado de su nodo ¡Esto no puede ser deshecho!", + deleted: "Eliminado", + deleted_description: "Eliminados todos los datos" + } + }, + encrypt: { + title: "Cambiar Contraseña", + caption: "Haga un respaldo primero para desbloquear el cifrado", + header: "Cifre sus palabras semilla", + hot_wallet_warning: + 'Mutiny es una "billetera caliente" por lo que necesita sus palabras semilla para operar, pero usted puede opcionalmente cifrar esas palabras con una contraseña.', + password_tip: + "De esa manera, si alguien consigue acceder a su navegador, de todas maneras no tendrá acceso a sus fondos.", + optional: "(opcional)", + existing_password: "Constraseña existente", + existing_password_caption: + "Deje vacío si no ha establecido una contraseña todavía.", + new_password_label: "Contraseña", + new_password_placeholder: "Ingrese una contraseña", + new_password_caption: + "Esta contraseña será usada para cifrar sus palabras semilla. Si se le olvida, necesitará reingresar sus palabras semilla para acceder a sus fondos. Usted si escribió sus palabras semilla ¿correcto?", + confirm_password_label: "Confirme Contraseña", + confirm_password_placeholder: "Ingrese la misma contraseña", + encrypt: "Cifrar", + skip: "Saltar", + error_match: "Las contraseñas no coinciden", + error_same_as_existingpassword: + "La nueva contraseña no debe coincidir con la contraseña existente" + }, + decrypt: { + title: "Ingrese su contraseña", + decrypt_wallet: "Descifrar Billetera", + forgot_password_link: "¿Olvidó la Contraseña?", + error_wrong_password: "Contraseña Inválida" + }, + currency: { + title: "Moneda", + caption: "Escoja su par de monedas preferida", + select_currency: "Seleccione Moneda", + select_currency_label: "Par de Moneda", + select_currency_caption: + "Al escoger una nueva moneda se resincronizará la billetera para obtener una actualización del precio", + request_currency_support_link: "Solicite soporte para más monedas", + error_unsupported_currency: + "Por favor seleccione una moneda soportada." + }, + language: { + title: "Idioma", + caption: "Escoja su idioma preferido", + select_language: "Seleccione Idioma", + select_language_label: "Idioma", + select_language_caption: + "Al escoger un nuevo idioma se cambiará el lenguaje de la billetera, ignorando el idioma actual del navegador", + request_language_support_link: "Solicite soporte para más idiomas", + error_unsupported_language: + "Por favor seleccione un idioma soportado." + }, + lnurl_auth: { + title: "LNURL Auth", + auth: "Auth", + expected: "Esperando algo como LNURL..." + }, + plus: { + title: "Mutiny+", + join: "Unirse", + sats_per_month: "por {{amount}} sats por mes.", + lightning_balance: + "Necesitará por lo menos {{amount}} sats en su saldo lightning para empezar ¡Intente antes de comprar!", + restore: "Restaurar Suscripción", + ready_to_join: "Listo para unirse", + click_confirm: "Haga clic en confirmar para pagar su primer mes.", + open_source: "Mutiny es código abierto y auto-hospedable.", + optional_pay: "Pero también puede pagar por él.", + paying_for: "Pagando por", + supports_dev: + "ayuda a soportar el desarrollo continuo y desbloquea el acceso temprano a nuevas características y funcionalidad premium:", + thanks: "¡Usted hace parte del motín! Disfrute de las siguientes ventajas:", + renewal_time: "Recibirá una solicitud de renovación de pago hacia", + cancel: "Para cancelar su suscripción simplemente no pague. También puede deshabilitar el Mutiny+", + wallet_connection: "Conexión Billetera.", + subscribe: "Suscribirse", + error_no_plan: "No se encontraron planes", + error_failure: "No se pudo suscribir", + error_no_subscription: "No se encontró ninguna suscripción", + error_expired_subscription: + "Su suscripción ha expirado, haga clic en unirse para renovar", + satisfaction: "Satisfacción engreída", + gifting: "Regalos", + multi_device: "Acceso multi-dispositivo", + ios_testflight: "Acceso a iOS TestFlight", + more: "... y más por venir", + cta_description: + "Disfrute acceso temprano a nuevas características y funcionalidad premium.", + cta_but_already_plus: "¡Gracias por su apoyo!" + }, + restore: { + title: "Restaurar", + all_twelve: "Necesita ingresar todas las 12 palabras", + wrong_word: "Palabra equivocada", + paste: "Peligrosamente Pegar del Portapapeles", + confirm_text: + "¿Está seguro de querer restaurar a esta billetera? ¡Su billetera existente será eliminada!", + restore_tip: + "Podrá restaurar una Billetera Mutiny existente desde su frase de 12 palabras semilla. Esto reemplazará su billetera existente, ¡por lo tanto esté seguro de saber lo que está haciendo!", + multi_browser_warning: + "No use múltiples navegadores al mismo tiempo.", + error_clipboard: "Portapapeles no soportado", + error_word_number: "Número equivocado de palabras", + error_invalid_seed: "Frase semilla inválida" + }, + servers: { + title: "Servidores", + caption: + "¡No confíe en nosotros! Use sus propios servidores para respaldar Mutiny.", + link: "Aprenda más acerca de auto-hospedaje", + proxy_label: "Proxy de Websockets", + proxy_caption: + "Cómo su nodo de lightning se comunica con el resto de la red.", + error_proxy: "Debe ser una url comenzando con wss://", + esplora_label: "Esplora", + esplora_caption: "Datos de bloques para información en-cadena.", + error_esplora: "Eso no parece una URL", + rgs_label: "RGS", + rgs_caption: + "Rapid Gossip Sync. Datos de red sobre la red de lightning usados para enrutamiento.", + error_rgs: "Eso no parece una URL", + lsp_label: "LSP", + lsp_caption: + "Lightning Service Provider. Automáticamente abre canales hacia usted para liquidez entrante. También envuelve facturas para privacidad.", + lsps_connection_string_label: + "Cadena de Caracteres de Conexión LSPS", + lsps_connection_string_caption: + "Lightning Service Provider. Automáticamente abre canales hacia usted para liquidez entrante. Usando la especificación LSP.", + error_lsps_connection_string: + "Eso no parece una cadena de caracteres de conexión", + lsps_token_label: "Token LSPS", + lsps_token_caption: + "Token LSPS. Usado para identificar qué billetera está conectando al LSP", + lsps_valid_error: + "Puede tener tan solo un LSP establecido o una Cadena de Caracteres de Conexión LSPS y Token LSPS establecidos, no ambos.", + error_lsps_token: "Eso no parece un token válido", + storage_label: "Almacenamiento", + storage_caption: "Servicio de respaldo VSS cifrado.", + error_lsp: "Eso no parece un URL", + save: "Guardar" + }, + nostr_contacts: { + title: "Sincronizar Contactos Nostr", + npub_label: "npub nostr", + npub_required: "Npub no puede estar vacío", + sync: "Sincronizar", + resync: "Resincronizar", + remove: "Eliminar" + }, + manage_federations: { + title: "Manejar Federaciones", + federation_code_label: "Código federación", + federation_code_required: "Código federación no puede estar vacío", + federation_added_success: "Federación agregada exitosamente", + federation_remove_confirm: + "¿Esta seguro de querer eliminar esta federación? Asegúrese primero de que sus fondos sean transferidos a su balance lightning u otra billetera.", + add: "Agregar", + remove: "Eliminar", + expires: "Expira", + federation_id: "ID federación", + description: + "Mutiny tiene soporte experimental para el protocolo Fedimint. Necesitará un código de invitación a la federación para poder usar esta funcionalidad. Estos fondos no están actualmente respaldados remotamente ¡Almacene fondos en una federación bajo su propio riesgo!", + learn_more: "Aprenda más sobre Fedimint." + }, + gift: { + give_sats_link: "Dar sats de regalo", + title: "Regalo", + no_plus_caption: "Actualice a Mutiny+ para habilitar regalos", + receive_too_small: + "Su primera recepción debe ser de {{amount}} SATS o más.", + setup_fee_lightning: + "Una comisión de instalación de lightning será cargada para recibir este regalo.", + already_claimed: "Este regalo ya ha sido reclamado", + sender_is_poor: + "El remitente no tiene suficiente saldo para pagar este regalo.", + sender_timed_out: + "Se agotó el tiempo para el pago del regalo. El remitente puede estar desconectado, o este regalo ya ha sido reclamado.", + sender_generic_error: "Remitente envió error: {{error}}", + receive_header: "¡Le han regalado algunos sats!", + receive_description: + "Usted debe ser bastante especial. Para reclamar su dinero simplemente presione el botón grande. Los fondos serán agregados a esta billetera la próxima vez que el remitente se conecte.", + receive_claimed: + "¡Regalo reclamado! Deberá ver el regalo reflejado en el balance en breve.", + receive_cta: "Reclamar Regalo", + receive_try_again: "Intente de Nuevo", + send_header: "Crear Regalo", + send_explainer: + "Regale sats. Cree una URL de regalo de Mutiny que pueda ser reclamado por cualquiera con un navegador.", + send_name_required: "Esto es para sus registros", + send_name_label: "Nombre del Recipiente", + send_header_claimed: "¡Regalo Recibido!", + send_claimed: "Su regalo ha sido reclamado. Gracias por compartir.", + send_sharable_header: "URL compartible", + send_instructions: + "Copie este URL de regalo a su recipiente, o pídale que escanee este código QR con su billetera.", + send_another: "Crear Otro", + send_small_warning: + "Un usuario nuevo de Mutiny no podrá redimir menos de 100k sats.", + send_cta: "Crear un regalo", + send_delete_button: "Eliminar Regalo", + send_delete_confirm: + "¿Está seguro de querer eliminar este regalo? ¿Es este su momento de tirar de la alfombra?", + send_tip: + "Su copia de la Billetera Mutiny necesita estar abierta para que el regalo sea reclamado.", + need_plus: + "Actualice a Mutiny+ para habilitar regalos. La funcionalidad de regalos le permite crear una URL de regalo de Mutiny que puede ser reclamado por cualquiera con un navegador." + } + }, + swap: { + peer_not_found: "Par no encontrado", + channel_too_small: + "Es simplemente tonto crear un canal más pequeño que {{amount}} sats", + insufficient_funds: "No tiene suficientes fondos para crear este canal", + header: "Hacer un Swap a Lightning", + initiated: "Swap Iniciado", + sats_added: "+{{amount}} sats serán agregados a su balance lightning", + use_existing: "Usar par existente", + choose_peer: "Escoja un par", + peer_connect_label: "Conectar a un par nuevo", + peer_connect_placeholder: "Cadena de caracteres de conexión a par", + connect: "Conectar", + connecting: "Conectando...", + confirm_swap: "Confirmar Swap" + }, + swap_lightning: { + insufficient_funds: + "No tiene fondos suficientes para hacer swap a lightning", + header: "Hacer Swap a Lightning", + header_preview: "Previsualizar Swap", + completed: "Swap Completado", + too_small: + "Monto invalido ingresado. Tiene que hacer un swap de por lo menos 100k sats.", + sats_added: + "+{{amount}} sats han sido agregados a su balance de Lightning", + sats_fee: "+{{amount}} sats de comisión", + confirm_swap: "Confirmar Swap", + preview_swap: "Previsualizar Comisión de Swap" + }, + reload: { + mutiny_update: "Actualización de Mutiny", + new_version_description: + "Una nueva versión de Mutiny ha sido almacenada en el caché, recargue para empezar a usarla.", + reload: "Recargar" + }, + error: { + title: "Error", + emergency_link: "kit de emergencia.", + reload: "Recargar", + restart: { + title: "¿Algo *extra* absurdo sucediendo? ¡Detenga los nodos!", + start: "Iniciar", + stop: "Parar" + }, + general: { + oh_no: "¡Oh no!", + never_should_happen: "Esto nunca debió suceder", + try_reloading: + 'Intente recargar esta página o haga clic en el botón "Qué Pena". Si continua teniendo problemas,', + support_link: "contáctenos para recibir soporte.", + getting_desperate: "¿Desesperado? Intente" + }, + load_time: { + stuck: "¿Atascado en esta pantalla? Intente recargar. Si eso no funciona, intente" + }, + not_found: { + title: "No Encontrado", + wtf_paul: "Esto es probablemente culpa de Paul." + }, + reset_router: { + payments_failing: + "¿No puede hacer pagos? Intente reiniciar el enrutador de lightning.", + reset_router: "Reiniciar Enrutador" + }, + resync: { + incorrect_balance: + "¿El balance en-cadena parece incorrecto? Intente resincronizar la billetera en-cadena.", + resync_wallet: "Resincronizar billetera" + }, + on_boot: { + existing_tab: { + title: "Múltiples pestañas detectadas", + description: + "Mutiny solo puede ser usado en una pestaña a la vez. Parece que tiene otra pestaña abierta con Mutiny ejecutándose. Por favor cierre esa pestaña y refresque esta página, o cierre esta pestaña y refresque la otra." + }, + already_running: { + title: "Mutiny puede estar ejecutándose en otro dispositivo", + description: + "Mutiny solo puede ser usado en un lugar a la vez. Parece que tiene otro dispositivo o navegador usando esta billetera. Si ha cerrado Mutiny recientemente en otro dispositivo, por favor espere unos minutos e intente de nuevo.", + retry_again_in: "Intente de nuevo en", + seconds: "segundos" + }, + incompatible_browser: { + title: "Navegador incompatible", + header: "Navegador incompatible detectado", + description: + "Mutiny requiere un navegador moderno que soporte WebAssembly, LocalStorage, e IndexedDB. Algunos navegadores deshabilitan estas funcionalidades en modo privado.", + try_different_browser: + 'Por favor asegúrese de que su navegador soporte todas estas funcionalidades, o considere intentar con otro navegador. También puede intentar deshabilitar ciertas extensiones o "escudos" que puedan bloquear estas funcionalidades.', + browser_storage: + "(Nos encantaría soportar más navegadores privados, pero tenemos que guardar los datos de su billetera en el almacenamiento del navegador o de lo contrario perdería esos fondos.)", + browsers_link: "Navegadores Soportados" + }, + loading_failed: { + title: "Fallo al cargar", + header: "Fallo al cargar Mutiny", + description: "Algo no funcionó al iniciar la Billetera Mutiny.", + repair_options: + "Si su billetera parece dañada, aquí hay algunas herramientas para tratar depurarla y repararla.", + questions: + "Si tiene alguna pregunta acerca de qué hacen estos botones, por favor", + support_link: "contáctenos para recibir soporte.", + services_down: + "Parece que uno de los servicios de Mutiny está abajo. Por favor intente de nuevo más tarde.", + in_the_meantime: + "Mientras tanto si desea acceder a sus fondos en-cadena puede cargar Mutiny en", + safe_mode: "Modo Seguro" + } + } + }, + modals: { + share: "Compartir", + details: "Detalles", + loading: { + loading: "Cargando: {{stage}}", + default: "Apenas empezando", + double_checking: "Verificando de nuevo algo", + downloading: "Descargando", + setup: "Configuración", + done: "Hecho" + }, + onboarding: { + welcome: "¡Bienvenido!", + restore_from_backup: + "Si ha usado Mutiny antes puede restaurar desde un respaldo ¡De lo contrario puede saltarse esto y disfrutar su nueva billetera!", + not_available: "Todavía no hacemos eso", + secure_your_funds: "Asegure sus fondos" + }, + more_info: { + whats_with_the_fees: "¿Cuál es el asunto con las comisiones?", + self_custodial: + "Mutiny es una billetera auto-custodial. Para iniciar un pago lightning debemos abrir un canal lightning, lo que requiere un monto mínimo y una comisión de instalación.", + future_payments: + "Pagos futuros, tanto envíos como recepciones, solamente incurrirán en comisiones de red y una comisión nominal de servicio a menos de que su canal se quede sin capacidad entrante.", + liquidity: "Aprenda más sobre liquidez" + }, + confirm_dialog: { + are_you_sure: "¿Está seguro?", + cancel: "Cancelar", + confirm: "Confirmar" + }, + lnurl_auth: { + auth_request: "Solicitud de autenticación", + login: "Acceder", + decline: "Declinar", + error: "Eso no funcionó por alguna razón.", + authenticated: "¡Autenticado!" + } + } +}; diff --git a/src/i18n/ko/translations.ts b/src/i18n/ko/translations.ts index a5ae3f20..0082b3f9 100644 --- a/src/i18n/ko/translations.ts +++ b/src/i18n/ko/translations.ts @@ -71,8 +71,6 @@ export default { "첫 라이트닝 받기는 {{amount}} SATS 이상이어야 합니다. 요청한 금액에서 설정 비용이 차감됩니다.", setup_fee_lightning: "라이트닝으로 지불하는 경우 라이트닝 설치 비용이 부과됩니다.", - too_big_for_beta: - "많은 SATS입니다. Mutiny Wallet이 여전히 베타 버전임을 알고 계시겠죠?", more_than_21m: "비트코인은 총 2,100만 개밖에 없습니다.", set_amount: "금액 설정", max: "최대", @@ -183,7 +181,7 @@ export default { header: "설정", support: "Mutiny 지원 방법 알아보기", general: "일반", - beta_features: "베타 기능", + experimental_features: "실험", debug_tools: "디버그 도구", danger_zone: "위험 지역", admin: { @@ -495,17 +493,6 @@ export default { not_available: "아직 이 기능은 지원하지 않습니다", secure_your_funds: "자금을 안전하게 보호하세요" }, - beta_warning: { - title: "경고: 베타 버전 소프트웨어", - beta_warning: - "저희가 여러분을 여기서 맞이할 수 있게 되어 기쁩니다. 그러나 경고하고 싶습니다: Mutiny Wallet은 베타 버전이며 여전히 버그와 미흡한 점이 있을 수 있습니다.", - be_careful: - "Mutiny에 지금보다 더 많은 자금을 투자하지 않도록 주의하세요.", - beta_Link: "베타 버전에 대해 자세히 알아보기", - pretend_money: - "위험 없이 Mutiny를 테스트하려면 가상 자금을 사용하려면", - signet_link: "Signet 버전을 확인하세요." - }, more_info: { whats_with_the_fees: "수수료는 어떻게 되나요?", self_custodial: diff --git a/src/router.tsx b/src/router.tsx index bb8407c6..713016f4 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -12,6 +12,7 @@ import { Main, NotFound, Receive, + Redeem, Scanner, Search, Send, @@ -100,6 +101,7 @@ export function Router() { + diff --git a/src/routes/Activity.tsx b/src/routes/Activity.tsx index 04ae3da0..8f1a631a 100644 --- a/src/routes/Activity.tsx +++ b/src/routes/Activity.tsx @@ -40,7 +40,7 @@ function ContactRow() { const [contacts, { refetch }] = createResource(async () => { try { const contacts: TagItem[] = - state.mutiny_wallet?.get_contacts_sorted(); + await state.mutiny_wallet?.get_contacts_sorted(); return contacts || []; } catch (e) { console.error(e); diff --git a/src/routes/Main.tsx b/src/routes/Main.tsx index 010b89f7..48d28395 100644 --- a/src/routes/Main.tsx +++ b/src/routes/Main.tsx @@ -5,7 +5,6 @@ import scan from "~/assets/icons/scan.svg"; import settings from "~/assets/icons/settings.svg"; import { BalanceBox, - BetaWarningModal, Card, CombinedActivity, DecryptDialog, @@ -106,7 +105,6 @@ export function Main() { - diff --git a/src/routes/Redeem.tsx b/src/routes/Redeem.tsx new file mode 100644 index 00000000..65c88725 --- /dev/null +++ b/src/routes/Redeem.tsx @@ -0,0 +1,248 @@ +import { LnUrlParams } from "@mutinywallet/mutiny-wasm"; +import { useNavigate } from "@solidjs/router"; +import { + createEffect, + createMemo, + createResource, + createSignal, + Match, + Show, + Suspense, + Switch +} from "solid-js"; + +import { + AmountEditable, + AmountFiat, + AmountSats, + BackLink, + Button, + DefaultMain, + InfoBox, + LargeHeader, + LoadingShimmer, + MegaCheck, + MutinyWalletGuard, + NavBar, + ReceiveWarnings, + showToast, + SuccessModal, + VStack +} from "~/components"; +import { useI18n } from "~/i18n/context"; +import { useMegaStore } from "~/state/megaStore"; +import { eify, vibrateSuccess } from "~/utils"; + +type RedeemState = "edit" | "paid"; + +export function Redeem() { + const [state, _actions] = useMegaStore(); + const navigate = useNavigate(); + const i18n = useI18n(); + + const [amount, setAmount] = createSignal(0n); + // const [whatForInput, setWhatForInput] = createSignal(""); + const [lnurlData, setLnUrlData] = createSignal(); + const [lnurlString, setLnUrlString] = createSignal(""); + const [fixedAmount, setFixedAmount] = createSignal(false); + + const [redeemState, setRedeemState] = createSignal("edit"); + + // loading state for the continue button + const [loading, setLoading] = createSignal(false); + const [error, setError] = createSignal(""); + + function mSatsToSats(mSats: bigint) { + return mSats / 1000n; + } + + function clearAll() { + setAmount(0n); + setLnUrlData(undefined); + setLnUrlString(""); + setFixedAmount(false); + setRedeemState("edit"); + setLoading(false); + setError(""); + } + + const [decodedLnurl] = createResource(async () => { + if (state.scan_result) { + if (state.scan_result.lnurl) { + const decoded = await state.mutiny_wallet?.decode_lnurl( + state.scan_result.lnurl + ); + return decoded; + } + } + }); + + createEffect(() => { + if (decodedLnurl()) { + processLnurl(decodedLnurl()!); + } + }); + + // A ParsedParams with an lnurl in it + async function processLnurl(decoded: LnUrlParams) { + if (decoded.tag === "withdrawRequest") { + if (decoded.min === decoded.max) { + console.log("fixed amount", decoded.max.toString()); + setAmount(mSatsToSats(decoded.max)); + setFixedAmount(true); + } else { + setAmount(mSatsToSats(decoded.min)); + setFixedAmount(false); + } + setLnUrlData(decoded); + setLnUrlString(state.scan_result?.lnurl || ""); + } + } + + const lnurlAmountText = createMemo(() => { + if (lnurlData()) { + return i18n.t("redeem.lnurl_amount_message", { + min: mSatsToSats(lnurlData()!.min).toLocaleString(), + max: mSatsToSats(lnurlData()!.max).toLocaleString() + }); + } + }); + + const canSend = createMemo(() => { + const lnurlParams = lnurlData(); + if (!lnurlParams) return false; + const min = mSatsToSats(lnurlParams.min); + const max = mSatsToSats(lnurlParams.max); + if (amount() === 0n || amount() < min || amount() > max) return false; + + return true; + }); + + async function handleLnUrlWithdrawal() { + const lnurlParams = lnurlData(); + if (!lnurlParams) return; + + setError(""); + setLoading(true); + + try { + const success = await state.mutiny_wallet?.lnurl_withdraw( + lnurlString(), + amount() + ); + if (!success) { + setError(i18n.t("redeem.lnurl_redeem_failed")); + } else { + setRedeemState("paid"); + await vibrateSuccess(); + } + } catch (e) { + console.error("lnurl_withdraw failed", e); + showToast(eify(e)); + } finally { + setLoading(false); + } + } + + return ( + + + + {i18n.t("redeem.redeem_bitcoin")} + + +
+ + + +
+ } + > + + + + + + + +

{lnurlAmountText()}

+
+
+ + +

{error()}

+
+
+ +
+ + {/* TODO: add tagging to lnurlwithdrawal and all the redeem flows */} + {/*
+ + setWhatForInput(e.currentTarget.value) + } + /> + */} + +
+ + + { + if (!open) clearAll(); + }} + onConfirm={() => { + clearAll(); + navigate("/"); + }} + > + +

+ {i18n.t("redeem.lnurl_redeem_success")} +

+
+
+ +
+
+ +
+
+ {/* TODO: add payment details */} +
+
NICE
+
+ + + + + ); +} diff --git a/src/routes/Search.tsx b/src/routes/Search.tsx index ec6cacf5..ddf9cf2a 100644 --- a/src/routes/Search.tsx +++ b/src/routes/Search.tsx @@ -87,7 +87,7 @@ function ActualSearch() { async function contactsFetcher() { try { const contacts: TagItem[] = - state.mutiny_wallet?.get_contacts_sorted(); + await state.mutiny_wallet?.get_contacts_sorted(); return contacts || []; } catch (e) { console.error(e); @@ -261,7 +261,7 @@ function ActualSearch() { type="text" value={searchValue()} onInput={(e) => setSearchValue(e.currentTarget.value)} - placeholder="Name, address, invoice..." + placeholder={i18n.t("send.search.placeholder")} autofocus ref={(el) => (searchInputRef = el)} /> @@ -271,7 +271,7 @@ function ActualSearch() { onClick={handlePaste} > Paste - Paste + {i18n.t("send.search.paste")} @@ -285,14 +285,16 @@ function ActualSearch() {
-

Contacts

+

+ {i18n.t("send.search.contacts")} +

}>

- Global Search + {i18n.t("send.search.global_search")}

void; foundNpubs: (string | undefined)[]; }) { + const i18n = useI18n(); const hexpubs = createMemo(() => { const hexpubs: Set = new Set(); for (const npub of props.foundNpubs) { @@ -403,7 +406,7 @@ function GlobalSearch(props: { } >

- No results found for "{props.searchValue}" + {i18n.t("send.search.no_results") + " " + props.searchValue}

diff --git a/src/routes/Send.tsx b/src/routes/Send.tsx index 6e856f8d..83a70cc6 100644 --- a/src/routes/Send.tsx +++ b/src/routes/Send.tsx @@ -361,6 +361,8 @@ export function Send() { const [parsingDestination, setParsingDestination] = createSignal(false); + const [decodingLnUrl, setDecodingLnUrl] = createSignal(false); + function handleDestination(source: ParsedParams | undefined) { if (!source) return; setParsingDestination(true); @@ -425,9 +427,11 @@ export function Send() { // A ParsedParams with an lnurl in it function processLnurl(source: ParsedParams & { lnurl: string }) { + setDecodingLnUrl(true); state.mutiny_wallet ?.decode_lnurl(source.lnurl) .then((lnurlParams) => { + setDecodingLnUrl(false); if (lnurlParams.tag === "payRequest") { if (lnurlParams.min == lnurlParams.max) { setAmountSats(lnurlParams.min / 1000n); @@ -447,6 +451,11 @@ export function Send() { setLnurlp(source.lnurl); setSource("lightning"); } + // TODO: this is a bit of a hack, ideally we do more nav from the megastore + if (lnurlParams.tag === "withdrawRequest") { + actions.setScanResult(source); + navigate("/redeem"); + } }) .catch((e) => showToast(eify(e))); } @@ -825,7 +834,7 @@ export function Send() {

{i18n.t("send.hodl_invoice_warning")}

- +

{error()}

diff --git a/src/routes/index.ts b/src/routes/index.ts index a47f92d4..d70096cf 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -9,3 +9,4 @@ export * from "./Send"; export * from "./Swap"; export * from "./SwapLightning"; export * from "./Search"; +export * from "./Redeem"; diff --git a/src/routes/settings/ManageFederations.tsx b/src/routes/settings/ManageFederations.tsx index 9a2a5b67..ddbe3709 100644 --- a/src/routes/settings/ManageFederations.tsx +++ b/src/routes/settings/ManageFederations.tsx @@ -17,6 +17,7 @@ import { Suspense, Switch } from "solid-js"; +import { QRCodeSVG } from "solid-qr-code"; import { AmountSats, @@ -50,6 +51,7 @@ export type MutinyFederationIdentity = { federation_name: string; welcome_message: string; federation_expiry_timestamp: number; + invite_code: string; }; type RefetchType = ( @@ -215,9 +217,17 @@ function FederationListItem(props: { > - + +
+ +
+ +
- + + + + + diff --git a/src/routes/settings/Plus.tsx b/src/routes/settings/Plus.tsx index c806d984..0b14124b 100644 --- a/src/routes/settings/Plus.tsx +++ b/src/routes/settings/Plus.tsx @@ -41,10 +41,10 @@ function Perks(props: { alreadySubbed?: boolean }) {
  • - {i18n.t("settings.plus.ios_beta_access")} + {i18n.t("settings.plus.ios_testflight")}
  • diff --git a/src/routes/settings/Root.tsx b/src/routes/settings/Root.tsx index d7e5e74c..e8654bad 100644 --- a/src/routes/settings/Root.tsx +++ b/src/routes/settings/Root.tsx @@ -107,10 +107,10 @@ export function Settings() { }, { href: "/settings/encrypt", - text: "Change Password", + text: i18n.t("settings.encrypt.title"), disabled: !state.has_backed_up, caption: !state.has_backed_up - ? "Backup first to unlock encryption" + ? i18n.t("settings.encrypt.caption") : undefined }, { @@ -131,7 +131,7 @@ export function Settings() { ]} /> diff --git a/src/routes/settings/SyncNostrContacts.tsx b/src/routes/settings/SyncNostrContacts.tsx index fb938d62..b5234ba4 100644 --- a/src/routes/settings/SyncNostrContacts.tsx +++ b/src/routes/settings/SyncNostrContacts.tsx @@ -90,6 +90,7 @@ function SyncContactsForm() { } export function SyncNostrContacts() { + const i18n = useI18n(); const [state, actions] = useMegaStore(); const [loading, setLoading] = createSignal(false); const [error, setError] = createSignal(); @@ -114,7 +115,9 @@ export function SyncNostrContacts() { - Sync Nostr Contacts + + {i18n.t("settings.nostr_contacts.title")} + @@ -135,13 +138,17 @@ export function SyncNostrContacts() { onClick={resync} loading={loading()} > - Resync + {i18n.t( + "settings.nostr_contacts.resync" + )} diff --git a/src/state/megaStore.tsx b/src/state/megaStore.tsx index e4e8b9e7..0bc104d3 100644 --- a/src/state/megaStore.tsx +++ b/src/state/megaStore.tsx @@ -69,7 +69,6 @@ type MegaStore = [ safe_mode?: boolean; npub?: string; preferredInvoiceType: "unified" | "lightning" | "onchain"; - betaWarned: boolean; testflightPromptDismissed: boolean; should_zap_hodl: boolean; federations?: MutinyFederationIdentity[]; @@ -94,7 +93,6 @@ type MegaStore = [ onError: (e: Error) => void, onSuccess: (value: ParsedParams) => void ): void; - setBetaWarned(): void; setTestFlightPromptDismissed(): void; toggleHodl(): void; dropMutinyWallet(): void; @@ -137,7 +135,6 @@ export const Provider: ParentComponent = (props) => { lang: localStorage.getItem("i18nexLng") || undefined, npub: localStorage.getItem("npub") || undefined, preferredInvoiceType: "unified" as "unified" | "lightning" | "onchain", - betaWarned: localStorage.getItem("betaWarned") === "true", should_zap_hodl: localStorage.getItem("should_zap_hodl") === "true", testflightPromptDismissed: localStorage.getItem("testflightPromptDismissed") === "true", @@ -410,10 +407,6 @@ export const Provider: ParentComponent = (props) => { } } }, - setBetaWarned() { - localStorage.setItem("betaWarned", "true"); - setState({ betaWarned: true }); - }, setTestFlightPromptDismissed() { localStorage.setItem("testflightPromptDismissed", "true"); setState({ testflightPromptDismissed: true }); diff --git a/src/utils/fetchZaps.ts b/src/utils/fetchZaps.ts index f2a85894..59b4739f 100644 --- a/src/utils/fetchZaps.ts +++ b/src/utils/fetchZaps.ts @@ -51,6 +51,21 @@ function findByTag(tags: string[][], tag: string): string | undefined { } } +function getZapKind(event: NostrEvent): "public" | "private" | "anonymous" { + const anonTag = event.tags.find((t) => { + if (t[0] === "anon") { + return true; + } + }); + + // If the anon field is empty it's anon, if it has other elements its private, otherwise it's public + if (anonTag) { + return anonTag.length < 2 ? "anonymous" : "private"; + } + + return "public"; +} + async function simpleZapFromEvent( event: NostrEvent, wallet: MutinyWallet @@ -63,8 +78,6 @@ async function simpleZapFromEvent( const from = request.pubkey; const content = request.content; - const anon = findByTag(request.tags, "anon"); - const bolt11 = findByTag(event.tags, "bolt11") || ""; if (!bolt11) { @@ -97,13 +110,7 @@ async function simpleZapFromEvent( } return { - // If the anon field is empty it's anon, if it has length it's private, otherwise it's public - kind: - typeof anon === "string" - ? anon.length - ? "private" - : "anonymous" - : "public", + kind: getZapKind(request), from_hexpub: from, to_hexpub: to, timestamp: BigInt(event.created_at), @@ -267,7 +274,7 @@ export const fetchZaps: ResourceFetcher< zaps.push(event); } } catch (e) { - console.error("Failed to parse zap event: ", object); + console.error("Failed to parse zap event: ", object, e); } } } diff --git a/src/utils/languages.ts b/src/utils/languages.ts index 1a7c6978..9f01cf50 100644 --- a/src/utils/languages.ts +++ b/src/utils/languages.ts @@ -9,12 +9,16 @@ export const EN_OPTION: Language = { }; export const LANGUAGE_OPTIONS: Language[] = [ + { + value: "Español", + shortName: "es" + }, { value: "Português", shortName: "pt" }, { - value: "Korean", + value: "한국어", shortName: "ko" } ]; diff --git a/tools/internal-rules/.eslintrc.js b/tools/internal-rules/.eslintrc.js new file mode 100644 index 00000000..769d038b --- /dev/null +++ b/tools/internal-rules/.eslintrc.js @@ -0,0 +1,19 @@ +"use strict"; + +module.exports = { + root: true, + extends: [ + "eslint:recommended", + "plugin:eslint-plugin/recommended", + "plugin:node/recommended" + ], + env: { + node: true + }, + overrides: [ + { + files: ["tests/**/*.js"], + env: { mocha: true } + } + ] +}; diff --git a/tools/internal-rules/lib/index.js b/tools/internal-rules/lib/index.js new file mode 100644 index 00000000..3bd8fc7c --- /dev/null +++ b/tools/internal-rules/lib/index.js @@ -0,0 +1,18 @@ +/** + * @fileoverview internal eslint rules + * @author + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const requireIndex = require("requireindex"); + +//------------------------------------------------------------------------------ +// Plugin Definition +//------------------------------------------------------------------------------ + +// import all rules in lib/rules +module.exports.rules = requireIndex(__dirname + "/rules"); diff --git a/tools/internal-rules/lib/rules/check-i18n-keys.js b/tools/internal-rules/lib/rules/check-i18n-keys.js new file mode 100644 index 00000000..7b61d877 --- /dev/null +++ b/tools/internal-rules/lib/rules/check-i18n-keys.js @@ -0,0 +1,98 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const { parse } = require("@babel/parser"); + +module.exports = { + meta: { + name: "check-i18n-keys", + type: "suggestion", + docs: { + description: + "Ensure translation keys in other language files match the keys in the English translation file.", + category: "Best Practices", + recommended: true + }, + fixable: null, + schema: [] + }, + create: function (context) { + function extractKeys(node, parentKey = "") { + const keys = []; + let properties = node.properties; + + if (typeof node === "string") { + const fileContent = fs.readFileSync(node, "utf8"); + const ast = parse(fileContent, { + sourceType: "module", + plugins: ["typescript", "jsx"] + }); + properties = + !!ast && ast.program.body[0].declaration.properties; + } + + function traverseProperties(properties, parentKey) { + properties.forEach((property) => { + if ( + (property.type === "ObjectProperty" || + property.type === "Property") && + property.key.type === "Identifier" + ) { + const currentKey = parentKey + ? `${parentKey}.${property.key.name}` + : property.key.name; + keys.push(currentKey); + if (property.value.type === "ObjectExpression") { + traverseProperties( + property.value.properties, + currentKey + ); + } + } + }); + } + + traverseProperties(properties, parentKey); + + return keys; + } + + return { + Program(node) { + for (const statement of node.body) { + const fallbackFilePath = path + .relative(process.cwd(), context.getFilename()) + .replace( + /\/i18n\/\w+\/translations\.ts$/, + "/i18n/en/translations.ts" + ); + + const keys = extractKeys(statement.declaration); + + const enKeys = extractKeys(fallbackFilePath); + + // Report missing keys + enKeys.forEach((enKey) => { + if (!keys.includes(enKey)) { + context.report({ + node: node, + message: `missing key '${enKey}'` + }); + } + }); + + // Report extra keys + keys.forEach((key) => { + if (!enKeys.includes(key)) { + context.report({ + node: node, + message: `extra key '${key}'` + }); + } + }); + } + } + }; + } +}; diff --git a/tools/internal-rules/package.json b/tools/internal-rules/package.json new file mode 100644 index 00000000..b19f160d --- /dev/null +++ b/tools/internal-rules/package.json @@ -0,0 +1,32 @@ +{ + "name": "eslint-plugin-internal-rules", + "version": "0.0.0", + "description": "internal eslint rules", + "keywords": [ + "eslint", + "eslintplugin", + "eslint-plugin" + ], + "author": "", + "main": "./lib/index.js", + "exports": "./lib/index.js", + "scripts": { + "lint": "npm-run-all \"lint:*\"", + "lint:js": "eslint ." + }, + "dependencies": { + "requireindex": "^1.2.0" + }, + "devDependencies": { + "eslint": "^8.19.0", + "eslint-plugin-eslint-plugin": "^5.0.0", + "eslint-plugin-node": "^11.1.0" + }, + "engines": { + "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" + }, + "peerDependencies": { + "eslint": ">=7" + }, + "license": "ISC" +} diff --git a/tools/internal-rules/pnpm-lock.yaml b/tools/internal-rules/pnpm-lock.yaml new file mode 100644 index 00000000..d72865c5 --- /dev/null +++ b/tools/internal-rules/pnpm-lock.yaml @@ -0,0 +1,961 @@ +lockfileVersion: "6.0" + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + requireindex: + specifier: ^1.2.0 + version: 1.2.0 + +devDependencies: + eslint: + specifier: ^8.19.0 + version: 8.19.0 + eslint-plugin-eslint-plugin: + specifier: ^5.0.0 + version: 5.0.0(eslint@8.19.0) + eslint-plugin-node: + specifier: ^11.1.0 + version: 11.1.0(eslint@8.19.0) + +packages: + /@aashutoshrathi/word-wrap@1.2.6: + resolution: + { + integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + } + engines: { node: ">=0.10.0" } + dev: true + + /@eslint/eslintrc@1.4.1: + resolution: + { + integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/config-array@0.9.5: + resolution: + { + integrity: sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== + } + engines: { node: ">=10.10.0" } + dependencies: + "@humanwhocodes/object-schema": 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/object-schema@1.2.1: + resolution: + { + integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + } + dev: true + + /acorn-jsx@5.3.2(acorn@8.11.3): + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.11.3 + dev: true + + /acorn@8.11.3: + resolution: + { + integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + } + engines: { node: ">=0.4.0" } + hasBin: true + dev: true + + /ajv@6.12.6: + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + } + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + } + engines: { node: ">=8" } + dev: true + + /ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + } + engines: { node: ">=8" } + dependencies: + color-convert: 2.0.1 + dev: true + + /argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + } + dev: true + + /balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + } + dev: true + + /brace-expansion@1.1.11: + resolution: + { + integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + } + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /callsites@3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + } + engines: { node: ">=6" } + dev: true + + /chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + } + engines: { node: ">=10" } + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + } + engines: { node: ">=7.0.0" } + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + } + dev: true + + /concat-map@0.0.1: + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + } + dev: true + + /cross-spawn@7.0.3: + resolution: + { + integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + } + engines: { node: ">= 8" } + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /debug@4.3.4: + resolution: + { + integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + } + engines: { node: ">=6.0" } + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /deep-is@0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + } + dev: true + + /doctrine@3.0.0: + resolution: + { + integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + } + engines: { node: ">=6.0.0" } + dependencies: + esutils: 2.0.3 + dev: true + + /escape-string-regexp@4.0.0: + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + } + engines: { node: ">=10" } + dev: true + + /eslint-plugin-es@3.0.1(eslint@8.19.0): + resolution: + { + integrity: sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== + } + engines: { node: ">=8.10.0" } + peerDependencies: + eslint: ">=4.19.1" + dependencies: + eslint: 8.19.0 + eslint-utils: 2.1.0 + regexpp: 3.2.0 + dev: true + + /eslint-plugin-eslint-plugin@5.0.0(eslint@8.19.0): + resolution: + { + integrity: sha512-4j1sOUmZEd6RsdRKjUbEwYLAcihOVlPLzX3jpLpp7d3lHM+nSlAXVAwCT8o2OfJ501LICr1v6OZo7b6tPzQkUQ== + } + engines: { node: ^14.17.0 || ^16.0.0 || >= 18.0.0 } + peerDependencies: + eslint: ">=7.0.0" + dependencies: + eslint: 8.19.0 + eslint-utils: 3.0.0(eslint@8.19.0) + estraverse: 5.3.0 + dev: true + + /eslint-plugin-node@11.1.0(eslint@8.19.0): + resolution: + { + integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== + } + engines: { node: ">=8.10.0" } + peerDependencies: + eslint: ">=5.16.0" + dependencies: + eslint: 8.19.0 + eslint-plugin-es: 3.0.1(eslint@8.19.0) + eslint-utils: 2.1.0 + ignore: 5.3.1 + minimatch: 3.1.2 + resolve: 1.22.8 + semver: 6.3.1 + dev: true + + /eslint-scope@7.2.2: + resolution: + { + integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-utils@2.1.0: + resolution: + { + integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + } + engines: { node: ">=6" } + dependencies: + eslint-visitor-keys: 1.3.0 + dev: true + + /eslint-utils@3.0.0(eslint@8.19.0): + resolution: + { + integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + } + engines: { node: ^10.0.0 || ^12.0.0 || >= 14.0.0 } + peerDependencies: + eslint: ">=5" + dependencies: + eslint: 8.19.0 + eslint-visitor-keys: 2.1.0 + dev: true + + /eslint-visitor-keys@1.3.0: + resolution: + { + integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + } + engines: { node: ">=4" } + dev: true + + /eslint-visitor-keys@2.1.0: + resolution: + { + integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + } + engines: { node: ">=10" } + dev: true + + /eslint-visitor-keys@3.4.3: + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + dev: true + + /eslint@8.19.0: + resolution: + { + integrity: sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + hasBin: true + dependencies: + "@eslint/eslintrc": 1.4.1 + "@humanwhocodes/config-array": 0.9.5 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-utils: 3.0.0(eslint@8.19.0) + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + functional-red-black-tree: 1.0.1 + glob-parent: 6.0.2 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + v8-compile-cache: 2.4.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree@9.6.1: + resolution: + { + integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint-visitor-keys: 3.4.3 + dev: true + + /esquery@1.5.0: + resolution: + { + integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + } + engines: { node: ">=0.10" } + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse@4.3.0: + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + } + engines: { node: ">=4.0" } + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse@5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + } + engines: { node: ">=4.0" } + dev: true + + /esutils@2.0.3: + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + } + engines: { node: ">=0.10.0" } + dev: true + + /fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + } + dev: true + + /fast-json-stable-stringify@2.1.0: + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + } + dev: true + + /fast-levenshtein@2.0.6: + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + } + dev: true + + /file-entry-cache@6.0.1: + resolution: + { + integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + } + engines: { node: ^10.12.0 || >=12.0.0 } + dependencies: + flat-cache: 3.2.0 + dev: true + + /flat-cache@3.2.0: + resolution: + { + integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + } + engines: { node: ^10.12.0 || >=12.0.0 } + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + rimraf: 3.0.2 + dev: true + + /flatted@3.3.1: + resolution: + { + integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + } + dev: true + + /fs.realpath@1.0.0: + resolution: + { + integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + } + dev: true + + /function-bind@1.1.2: + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + } + dev: true + + /functional-red-black-tree@1.0.1: + resolution: + { + integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + } + dev: true + + /glob-parent@6.0.2: + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + } + engines: { node: ">=10.13.0" } + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.3: + resolution: + { + integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + } + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /globals@13.24.0: + resolution: + { + integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + } + engines: { node: ">=8" } + dependencies: + type-fest: 0.20.2 + dev: true + + /has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + } + engines: { node: ">=8" } + dev: true + + /hasown@2.0.1: + resolution: + { + integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA== + } + engines: { node: ">= 0.4" } + dependencies: + function-bind: 1.1.2 + dev: true + + /ignore@5.3.1: + resolution: + { + integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + } + engines: { node: ">= 4" } + dev: true + + /import-fresh@3.3.0: + resolution: + { + integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + } + engines: { node: ">=6" } + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /imurmurhash@0.1.4: + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + } + engines: { node: ">=0.8.19" } + dev: true + + /inflight@1.0.6: + resolution: + { + integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + } + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + } + dev: true + + /is-core-module@2.13.1: + resolution: + { + integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + } + dependencies: + hasown: 2.0.1 + dev: true + + /is-extglob@2.1.1: + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + } + engines: { node: ">=0.10.0" } + dev: true + + /is-glob@4.0.3: + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + } + engines: { node: ">=0.10.0" } + dependencies: + is-extglob: 2.1.1 + dev: true + + /isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + } + dev: true + + /js-yaml@4.1.0: + resolution: + { + integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + } + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + } + dev: true + + /json-schema-traverse@0.4.1: + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + } + dev: true + + /json-stable-stringify-without-jsonify@1.0.1: + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + } + dev: true + + /keyv@4.5.4: + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + } + dependencies: + json-buffer: 3.0.1 + dev: true + + /levn@0.4.1: + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + } + engines: { node: ">= 0.8.0" } + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /lodash.merge@4.6.2: + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + } + dev: true + + /minimatch@3.1.2: + resolution: + { + integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + } + dependencies: + brace-expansion: 1.1.11 + dev: true + + /ms@2.1.2: + resolution: + { + integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + } + dev: true + + /natural-compare@1.4.0: + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + } + dev: true + + /once@1.4.0: + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + } + dependencies: + wrappy: 1.0.2 + dev: true + + /optionator@0.9.3: + resolution: + { + integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + } + engines: { node: ">= 0.8.0" } + dependencies: + "@aashutoshrathi/word-wrap": 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /parent-module@1.0.1: + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + } + engines: { node: ">=6" } + dependencies: + callsites: 3.1.0 + dev: true + + /path-is-absolute@1.0.1: + resolution: + { + integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + } + engines: { node: ">=0.10.0" } + dev: true + + /path-key@3.1.1: + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + } + engines: { node: ">=8" } + dev: true + + /path-parse@1.0.7: + resolution: + { + integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + } + dev: true + + /prelude-ls@1.2.1: + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + } + engines: { node: ">= 0.8.0" } + dev: true + + /punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + } + engines: { node: ">=6" } + dev: true + + /regexpp@3.2.0: + resolution: + { + integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + } + engines: { node: ">=8" } + dev: true + + /requireindex@1.2.0: + resolution: + { + integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== + } + engines: { node: ">=0.10.5" } + dev: false + + /resolve-from@4.0.0: + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + } + engines: { node: ">=4" } + dev: true + + /resolve@1.22.8: + resolution: + { + integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + } + hasBin: true + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /rimraf@3.0.2: + resolution: + { + integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + } + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /semver@6.3.1: + resolution: + { + integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + } + hasBin: true + dev: true + + /shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + } + engines: { node: ">=8" } + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + } + engines: { node: ">=8" } + dev: true + + /strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + } + engines: { node: ">=8" } + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-json-comments@3.1.1: + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + } + engines: { node: ">=8" } + dev: true + + /supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + } + engines: { node: ">=8" } + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-preserve-symlinks-flag@1.0.0: + resolution: + { + integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + } + engines: { node: ">= 0.4" } + dev: true + + /text-table@0.2.0: + resolution: + { + integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + } + dev: true + + /type-check@0.4.0: + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + } + engines: { node: ">= 0.8.0" } + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-fest@0.20.2: + resolution: + { + integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + } + engines: { node: ">=10" } + dev: true + + /uri-js@4.4.1: + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + } + dependencies: + punycode: 2.3.1 + dev: true + + /v8-compile-cache@2.4.0: + resolution: + { + integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== + } + dev: true + + /which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + } + engines: { node: ">= 8" } + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /wrappy@1.0.2: + resolution: + { + integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + } + dev: true