diff --git a/bin/release-note-generator.ts b/bin/release-note-generator.ts index de1346f9b1..4a0395241e 100644 --- a/bin/release-note-generator.ts +++ b/bin/release-note-generator.ts @@ -6,7 +6,7 @@ import prompts from 'prompts'; async function run() { const username = await execAsync( - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography "gh api user --jq '.login'", 'To avoid having to enter your username, consider installing the official GitHub CLI (https://github.com/cli/cli) and logging in with `gh auth login`.', ); diff --git a/eslint.config.mjs b/eslint.config.mjs index 0c9aa15e51..eea22bda24 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,29 +1,15 @@ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - import globals from 'globals'; import pluginImport from 'eslint-plugin-import'; import pluginJSXA11y from 'eslint-plugin-jsx-a11y'; import pluginReact from 'eslint-plugin-react'; import pluginReactHooks from 'eslint-plugin-react-hooks'; -import pluginRulesDir from 'eslint-plugin-rulesdir'; import pluginTypescript from 'typescript-eslint'; import pluginTypescriptPaths from 'eslint-plugin-typescript-paths'; +import pluginActual from './packages/eslint-plugin-actual/lib/index.js'; import tsParser from '@typescript-eslint/parser'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -pluginRulesDir.RULES_DIR = path.join( - __dirname, - 'packages', - 'eslint-plugin-actual', - 'lib', - 'rules', -); - const confusingBrowserGlobals = [ // https://github.com/facebook/create-react-app/tree/main/packages/confusing-browser-globals 'addEventListener', @@ -167,11 +153,15 @@ export default pluginTypescript.config( pluginImport.flatConfigs.recommended, { plugins: { + actual: pluginActual, 'react-hooks': pluginReactHooks, 'jsx-a11y': pluginJSXA11y, - rulesdir: pluginRulesDir, 'typescript-paths': pluginTypescriptPaths, }, + rules: { + 'actual/no-untranslated-strings': 'error', + 'actual/prefer-trans-over-t': 'error', + }, }, { files: ['**/*.{js,ts,jsx,tsx}'], @@ -458,8 +448,8 @@ export default pluginTypescript.config( }, ], - 'rulesdir/typography': 'warn', - 'rulesdir/prefer-if-statement': 'warn', + 'actual/typography': 'warn', + 'actual/prefer-if-statement': 'warn', // Note: base rule explicitly disabled in favor of the TS one 'no-unused-vars': 'off', @@ -778,7 +768,8 @@ export default pluginTypescript.config( ], rules: { - 'rulesdir/typography': 'off', + 'actual/typography': 'off', + 'actual/no-untranslated-strings': 'off', }, }, { @@ -797,7 +788,7 @@ export default pluginTypescript.config( // TODO: fix the issues in these files rules: { 'import/extensions': 'off', - 'rulesdir/typography': 'off', + 'actual/typography': 'off', }, }, { @@ -805,8 +796,6 @@ export default pluginTypescript.config( rules: { 'import/no-anonymous-default-export': 'off', 'import/no-default-export': 'off', - // can be re-enabled after https://github.com/actualbudget/actual/pull/4253 - '@typescript-eslint/no-unused-vars': 'off', }, }, ); diff --git a/package.json b/package.json index 83123ec320..b628494686 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-rulesdir": "^0.2.2", "eslint-plugin-typescript-paths": "^0.0.33", "globals": "^15.15.0", "html-to-image": "^1.11.13", diff --git a/packages/component-library/src/Toggle.tsx b/packages/component-library/src/Toggle.tsx index ae9d1cb63e..ee055edf8b 100644 --- a/packages/component-library/src/Toggle.tsx +++ b/packages/component-library/src/Toggle.tsx @@ -62,7 +62,7 @@ export const Toggle = ({ data-on={isOn} className={css( { - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography content: '" "', position: 'absolute', top: '2px', diff --git a/packages/component-library/src/styles.ts b/packages/component-library/src/styles.ts index ea21ddd0a9..0e5d0eeead 100644 --- a/packages/component-library/src/styles.ts +++ b/packages/component-library/src/styles.ts @@ -91,7 +91,7 @@ export const styles: Record = { }, shadowLarge, tnum: { - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography fontFeatureSettings: '"tnum"', }, notFixed: { fontFeatureSettings: '' }, diff --git a/packages/desktop-client/e2e/fixtures.ts b/packages/desktop-client/e2e/fixtures.ts index 68efa9104b..0c9dc10b5a 100644 --- a/packages/desktop-client/e2e/fixtures.ts +++ b/packages/desktop-client/e2e/fixtures.ts @@ -14,7 +14,7 @@ export const expect = baseExpect.extend({ } const config = { - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography mask: [locator.locator('[data-vrt-mask="true"]')], maxDiffPixels: 5, }; diff --git a/packages/desktop-client/src/auth/ProtectedRoute.tsx b/packages/desktop-client/src/auth/ProtectedRoute.tsx index 911d88ff12..9b65272899 100644 --- a/packages/desktop-client/src/auth/ProtectedRoute.tsx +++ b/packages/desktop-client/src/auth/ProtectedRoute.tsx @@ -1,4 +1,5 @@ import { useEffect, useState, type ReactElement } from 'react'; +import { Trans } from 'react-i18next'; import { View } from '@actual-app/components/view'; @@ -60,7 +61,9 @@ export const ProtectedRoute = ({ margin: '50px', }} > -

You don't have permission to view this page

+

+ You don’t have permission to view this page +

); }; diff --git a/packages/desktop-client/src/components/CommandBar.tsx b/packages/desktop-client/src/components/CommandBar.tsx index 02291e06c5..83d8627077 100644 --- a/packages/desktop-client/src/components/CommandBar.tsx +++ b/packages/desktop-client/src/components/CommandBar.tsx @@ -245,7 +245,7 @@ export function CommandBar() { 'var(--color-menuItemBackgroundHover)', color: 'var(--color-menuItemTextHover)', }, - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography "&[data-selected='true']": { backgroundColor: 'var(--color-menuItemBackgroundHover)', color: 'var(--color-menuItemTextHover)', diff --git a/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx b/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx index 6d26b8946a..997f055b76 100644 --- a/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx +++ b/packages/desktop-client/src/components/budget/envelope/EnvelopeBudgetComponents.tsx @@ -114,13 +114,17 @@ export const BudgetTotalsMonth = memo(function BudgetTotalsMonth() { - Spent + + Spent + {props => } - Balance + + Balance + { display: 'block', background: theme.checkboxBackgroundSelected + - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography ' url(\'data:image/svg+xml; utf8,\') 9px 9px', width: 9, height: 9, - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography content: '" "', }, }, @@ -124,7 +124,7 @@ export const Checkbox = (props: CheckboxProps) => { right: -5, border: '2px solid ' + theme.checkboxBorderSelected, borderRadius: 6, - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography content: '" "', }, }, diff --git a/packages/desktop-client/src/components/mobile/MobileBackButton.tsx b/packages/desktop-client/src/components/mobile/MobileBackButton.tsx index 8d72f86ee0..63b36e1034 100644 --- a/packages/desktop-client/src/components/mobile/MobileBackButton.tsx +++ b/packages/desktop-client/src/components/mobile/MobileBackButton.tsx @@ -1,5 +1,5 @@ import React, { type ComponentPropsWithoutRef } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans } from 'react-i18next'; import { Button } from '@actual-app/components/button'; import { SvgCheveronLeft } from '@actual-app/components/icons/v1'; @@ -15,7 +15,6 @@ export function MobileBackButton({ style, ...props }: MobileBackButtonProps) { - const { t } = useTranslation(); const navigate = useNavigate(); return ( ); diff --git a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx index 5859540b00..ffcc783097 100644 --- a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx @@ -5,7 +5,7 @@ import { useRef, useState, } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { Button } from '@actual-app/components/button'; import { @@ -190,7 +190,7 @@ export function AccountMenuModal({ height={20} style={{ paddingRight: 5 }} /> - {t('Edit notes')} + Edit notes diff --git a/packages/desktop-client/src/components/modals/BudgetFileSelectionModal.tsx b/packages/desktop-client/src/components/modals/BudgetFileSelectionModal.tsx index f172769eb9..44e13913a0 100644 --- a/packages/desktop-client/src/components/modals/BudgetFileSelectionModal.tsx +++ b/packages/desktop-client/src/components/modals/BudgetFileSelectionModal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { Text } from '@actual-app/components/text'; import { View } from '@actual-app/components/view'; @@ -36,7 +36,7 @@ export function BudgetFileSelectionModal() { }} > - {t('Switching from:')} + Switching from: {currentFile?.name} diff --git a/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx b/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx index a67a6c123a..bb3858aef0 100644 --- a/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/CategoryGroupMenuModal.tsx @@ -5,7 +5,7 @@ import React, { useState, type CSSProperties, } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { Button } from '@actual-app/components/button'; import { @@ -49,7 +49,6 @@ export function CategoryGroupMenuModal({ onToggleVisibility, onClose, }: CategoryGroupMenuModalProps) { - const { t } = useTranslation(); const { grouped: categoryGroups } = useCategories(); const group = categoryGroups.find(g => g.id === groupId); const notes = useNotes(group.id); @@ -156,7 +155,7 @@ export function CategoryGroupMenuModal({ > diff --git a/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx b/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx index 11ef6ecb55..a999110ed0 100644 --- a/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/CategoryMenuModal.tsx @@ -1,6 +1,6 @@ // @ts-strict-ignore import React, { useRef, useState, type CSSProperties } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { Button } from '@actual-app/components/button'; import { @@ -148,7 +148,7 @@ export function CategoryMenuModal({ height={20} style={{ paddingRight: 5 }} /> - {t('Edit notes')} + Edit notes diff --git a/packages/desktop-client/src/components/modals/ConfirmCategoryDeleteModal.tsx b/packages/desktop-client/src/components/modals/ConfirmCategoryDeleteModal.tsx index 19032d0c9b..304b6f90d0 100644 --- a/packages/desktop-client/src/components/modals/ConfirmCategoryDeleteModal.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmCategoryDeleteModal.tsx @@ -1,6 +1,6 @@ // @ts-strict-ignore import React, { useState } from 'react'; -import { useTranslation } from 'react-i18next'; // Import useTranslation +import { Trans, useTranslation } from 'react-i18next'; // Import useTranslation import { Block } from '@actual-app/components/block'; import { Button } from '@actual-app/components/button'; @@ -8,6 +8,8 @@ import { Text } from '@actual-app/components/text'; import { theme } from '@actual-app/components/theme'; import { View } from '@actual-app/components/view'; +import { type TransObjectLiteral } from 'loot-core/types/util'; + import { CategoryAutocomplete } from '@desktop-client/components/autocomplete/CategoryAutocomplete'; import { Modal, @@ -73,23 +75,53 @@ export function ConfirmCategoryDeleteModal({ {group ? ( - Categories in the group {group.name} are used - by existing transactions - {!isIncome && - ' or it has a positive leftover balance currently'} - . Are you sure you want to delete it? If so, - you must select another category to transfer existing - transactions and balance to. + {!isIncome ? ( + + Categories in the group{' '} + + {{ group: group.name } as TransObjectLiteral} + {' '} + are used by existing transactions. + + ) : ( + + Categories in the group{' '} + + {{ group: group.name } as TransObjectLiteral} + {' '} + are used by existing transactions or it has a positive + leftover balance currently. + + )} + + Are you sure you want to delete it? If so, + you must select another category to transfer existing + transactions and balance to. + ) : ( - {category.name} is used by existing - transactions - {!isIncome && - ' or it has a positive leftover balance currently'} - . Are you sure you want to delete it? If so, - you must select another category to transfer existing - transactions and balance to. + {!isIncome ? ( + + + {{ category: category.name } as TransObjectLiteral} + {' '} + is used by existing transactions. + + ) : ( + + + {{ category: category.name } as TransObjectLiteral} + {' '} + is used by existing transactions or it has a positive + leftover balance currently. + + )} + + Are you sure you want to delete it? If so, + you must select another category to transfer existing + transactions and balance to. + )} @@ -103,7 +135,9 @@ export function ConfirmCategoryDeleteModal({ alignItems: 'center', }} > - {t('Transfer to:')} + + Transfer to: + - {t('Delete')} + Delete diff --git a/packages/desktop-client/src/components/modals/ConfirmTransactionDeleteModal.tsx b/packages/desktop-client/src/components/modals/ConfirmTransactionDeleteModal.tsx index 5d606a5c62..15b55e2ebe 100644 --- a/packages/desktop-client/src/components/modals/ConfirmTransactionDeleteModal.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmTransactionDeleteModal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { Button } from '@actual-app/components/button'; import { useResponsive } from '@actual-app/components/hooks/useResponsive'; @@ -55,7 +55,7 @@ export function ConfirmTransactionDeleteModal({ }} onPress={close} > - {t('Cancel')} + Cancel diff --git a/packages/desktop-client/src/components/modals/ConfirmUnlinkAccountModal.tsx b/packages/desktop-client/src/components/modals/ConfirmUnlinkAccountModal.tsx index d99a1ae7b8..a4c81d13de 100644 --- a/packages/desktop-client/src/components/modals/ConfirmUnlinkAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmUnlinkAccountModal.tsx @@ -60,7 +60,7 @@ export function ConfirmUnlinkAccountModal({ }} > diff --git a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx index 9e0b6db09f..2e29489056 100644 --- a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx @@ -359,7 +359,7 @@ export function CreateAccountModal({ }} onPress={onCreateLocalAccount} > - {t('Create a local account')} + Create a local account diff --git a/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx b/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx index 63c5516a91..b04259b1b4 100644 --- a/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx @@ -103,7 +103,7 @@ export function CreateEncryptionKeyModal({ to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" linkColor="purple" > - {t('Learn more')} + Learn more diff --git a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx index ac1dbd6a39..01bdb587c8 100644 --- a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx @@ -179,18 +179,20 @@ export function CreateLocalAccountModal() { {balanceError && ( - {t('Balance must be a number')} + Balance must be a number )} - + diff --git a/packages/desktop-client/src/components/modals/EditAccess.tsx b/packages/desktop-client/src/components/modals/EditAccess.tsx index 59e31a5e49..dafcb146b6 100644 --- a/packages/desktop-client/src/components/modals/EditAccess.tsx +++ b/packages/desktop-client/src/components/modals/EditAccess.tsx @@ -149,7 +149,7 @@ export function EditUserAccess({ style={{ marginRight: 10 }} onPress={() => dispatch(popModal())} > - Cancel + Cancel - + diff --git a/packages/desktop-client/src/components/modals/EnvelopeBalanceMenuModal.tsx b/packages/desktop-client/src/components/modals/EnvelopeBalanceMenuModal.tsx index a7b7581f4a..44ad20e3a1 100644 --- a/packages/desktop-client/src/components/modals/EnvelopeBalanceMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/EnvelopeBalanceMenuModal.tsx @@ -1,5 +1,5 @@ import React, { type CSSProperties } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans } from 'react-i18next'; import { styles } from '@actual-app/components/styles'; import { Text } from '@actual-app/components/text'; @@ -40,7 +40,6 @@ export function EnvelopeBalanceMenuModal({ borderTop: `1px solid ${theme.pillBorder}`, }; - const { t } = useTranslation(); const category = useCategory(categoryId); if (!category) { @@ -68,7 +67,7 @@ export function EnvelopeBalanceMenuModal({ fontWeight: 400, }} > - {t('Balance')} + Balance - {t('Budgeted')} + Budgeted - {t('Edit notes')} + Edit notes @@ -155,7 +155,7 @@ export function EnvelopeBudgetMonthMenuModal({ style={{ paddingRight: 5 }} /> )} - {t('Actions')} + Actions diff --git a/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx b/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx index 235aefed5b..290c76e024 100644 --- a/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx +++ b/packages/desktop-client/src/components/modals/FixEncryptionKeyModal.tsx @@ -1,7 +1,7 @@ // @ts-strict-ignore import React, { useState } from 'react'; import { Form } from 'react-aria-components'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { Button, ButtonWithLoading } from '@actual-app/components/button'; import { useResponsive } from '@actual-app/components/hooks/useResponsive'; @@ -91,7 +91,7 @@ export function FixEncryptionKeyModal({ variant="external" to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" > - {t('Learn more')} + Learn more ) : ( @@ -103,7 +103,7 @@ export function FixEncryptionKeyModal({ variant="external" to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" > - {t('Learn more')} + Learn more )} @@ -122,7 +122,7 @@ export function FixEncryptionKeyModal({ }} > - {t('Password')} + Password {' '} {error && ( setShowPassword(!showPassword)} />{' '} - {t('Show password')} + Show password @@ -166,7 +166,7 @@ export function FixEncryptionKeyModal({ }} onPress={close} > - {t('Back')} + Back _onSubmit(amount)} > - {t('Hold')} + Hold diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx index e21203b4f6..34e1ec1e12 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx @@ -807,7 +807,7 @@ export function ImportTransactionsModal({ fontStyle: 'italic', }} > - {t('No transactions found')} + No transactions found ); }} @@ -914,7 +914,7 @@ export function ImportTransactionsModal({ setReconcile(!reconcile); }} > - {t('Merge with existing transactions')} + Merge with existing transactions )} @@ -954,7 +954,7 @@ export function ImportTransactionsModal({ alignItems: 'baseline', }} > - {t('Delimiter:')} + Delimiter: \') 9px 9px', }, }, @@ -143,11 +143,11 @@ export function Transaction({ background: theme.buttonNormalDisabledBorder + // minus sign adapted from packages/desktop-client/src/icons/v1/add.svg - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography ' url(\'data:image/svg+xml; utf8,\') 9px 9px', width: 9, height: 9, - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography content: '" "', }, }, @@ -158,7 +158,7 @@ export function Transaction({ background: theme.checkboxBackgroundSelected + // plus sign from packages/desktop-client/src/icons/v1/add.svg - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography ' url(\'data:image/svg+xml; utf8,\') 9px 9px', }, }, diff --git a/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx b/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx index dead5fb23a..3c876eef92 100644 --- a/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx +++ b/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, useEffect, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans } from 'react-i18next'; import { Button } from '@actual-app/components/button'; import { Paragraph } from '@actual-app/components/paragraph'; @@ -9,6 +9,7 @@ import { View } from '@actual-app/components/view'; import { send } from 'loot-core/platform/client/fetch'; import { type PayeeEntity } from 'loot-core/types/models'; +import { type TransObjectLiteral } from 'loot-core/types/util'; import { Information } from '@desktop-client/components/alerts'; import { Modal, ModalButtons } from '@desktop-client/components/common/Modal'; @@ -30,7 +31,6 @@ export function MergeUnusedPayeesModal({ payeeIds, targetPayeeId, }: MergeUnusedPayeesModalProps) { - const { t } = useTranslation(); const allPayees = usePayees(); const modalStack = useSelector(state => state.modals.modalStack); const isEditingRule = !!modalStack.find(m => m.name === 'edit-rule'); @@ -109,16 +109,28 @@ export function MergeUnusedPayeesModal({ {payees.length === 1 ? ( - <> - The payee {payees[0].name}{' '} - is not used by transactions any more. Would like to merge it - with {targetPayee.name}? - + + The payee{' '} + + {{ payee: payees[0].name } as TransObjectLiteral} + {' '} + is not used by transactions any more. Would you like to merge + it with{' '} + + {{ payee: targetPayee.name } as TransObjectLiteral} + + ? + ) : ( <> - The following payees are not used by transactions any more. - Would like to merge them with{' '} - {targetPayee.name}? + + The following payees are not used by transactions any more. + Would you like to merge them with{' '} + + {{ payee: targetPayee.name } as TransObjectLiteral} + + ? +
    - {t( - 'Merging will remove the payee and transfer any existing rules to the new payee.', - )} + + Merging will remove the payee and transfer any existing rules to + the new payee. + {!isEditingRule && ( - <> - {' '} + If checked below, a rule will be created to do this rename while importing transactions. - + )} @@ -169,9 +181,9 @@ export function MergeUnusedPayeesModal({ onChange={e => setShouldCreateRule(e.target.checked)} /> - Automatically rename{' '} - {payees.length === 1 ? 'this payee' : 'these payees'} in the - future + + Automatically rename these payees in the future + )} @@ -186,7 +198,7 @@ export function MergeUnusedPayeesModal({ close(); }} > - {t('Merge')} + Merge {!isEditingRule && ( )} diff --git a/packages/desktop-client/src/components/modals/NotesModal.tsx b/packages/desktop-client/src/components/modals/NotesModal.tsx index 808edb4c29..1e7ca0db1f 100644 --- a/packages/desktop-client/src/components/modals/NotesModal.tsx +++ b/packages/desktop-client/src/components/modals/NotesModal.tsx @@ -1,6 +1,6 @@ // @ts-strict-ignore import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { Button } from '@actual-app/components/button'; import { SvgCheck } from '@actual-app/components/icons/v2'; @@ -82,7 +82,7 @@ export function NotesModal({ id, name, onSave }: NotesModalProps) { }} > - {t('Save notes')} + Save notes diff --git a/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx b/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx index a395c38ba4..1b69e6cc22 100644 --- a/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx @@ -3,7 +3,7 @@ import React, { type ComponentPropsWithoutRef, type CSSProperties, } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { Menu } from '@actual-app/components/menu'; import { styles } from '@actual-app/components/styles'; @@ -40,7 +40,6 @@ export function ScheduledTransactionMenuModal({ onComplete, }: ScheduledTransactionMenuModalProps) { const locale = useLocale(); - const { t } = useTranslation(); const defaultMenuItemStyle: CSSProperties = { ...styles.mobileMenuItem, color: theme.menuItemText, @@ -82,7 +81,7 @@ export function ScheduledTransactionMenuModal({ }} > - {t('Scheduled date')} + Scheduled date {format(schedule?.next_date || '', 'MMMM dd, yyyy', locale)} diff --git a/packages/desktop-client/src/components/modals/TrackingBalanceMenuModal.tsx b/packages/desktop-client/src/components/modals/TrackingBalanceMenuModal.tsx index e3c0885d9d..a61eb19520 100644 --- a/packages/desktop-client/src/components/modals/TrackingBalanceMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/TrackingBalanceMenuModal.tsx @@ -1,5 +1,5 @@ import React, { type CSSProperties } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans } from 'react-i18next'; import { styles } from '@actual-app/components/styles'; import { Text } from '@actual-app/components/text'; @@ -38,7 +38,6 @@ export function TrackingBalanceMenuModal({ borderTop: `1px solid ${theme.pillBorder}`, }; - const { t } = useTranslation(); const category = useCategory(categoryId); if (!category) { @@ -66,7 +65,7 @@ export function TrackingBalanceMenuModal({ fontWeight: 400, }} > - {t('Balance')} + Balance - {t('Budgeted')} + Budgeted - {t('Edit notes')} + Edit notes @@ -157,7 +157,7 @@ export function TrackingBudgetMonthMenuModal({ style={{ paddingRight: 5 }} /> )} - {t('Actions')} + Actions diff --git a/packages/desktop-client/src/components/modals/TransferOwnership.tsx b/packages/desktop-client/src/components/modals/TransferOwnership.tsx index cea034d7ab..32d0619a39 100644 --- a/packages/desktop-client/src/components/modals/TransferOwnership.tsx +++ b/packages/desktop-client/src/components/modals/TransferOwnership.tsx @@ -132,9 +132,10 @@ export function TransferOwnership({ marginTop: 5, }} > - {t( - 'Select a user from the directory to designate as the new budget owner.', - )} + + Select a user from the directory to designate as the new + budget owner. + )} @@ -166,7 +167,7 @@ export function TransferOwnership({ marginTop: 5, }} > - {t('No users available')} + No users available )} diff --git a/packages/desktop-client/src/components/payees/ManagePayees.tsx b/packages/desktop-client/src/components/payees/ManagePayees.tsx index 37c08f85a2..fa2bb6429f 100644 --- a/packages/desktop-client/src/components/payees/ManagePayees.tsx +++ b/packages/desktop-client/src/components/payees/ManagePayees.tsx @@ -313,7 +313,7 @@ export const ManagePayees = ({ marginTop: 5, }} > - {t('No payees')} + No payees ) : ( - Live + Live {t('Current month')}} + content={ + + Current month + + } style={{ ...styles.tooltip, lineHeight: 1.5, diff --git a/packages/desktop-client/src/components/reports/ReportSummary.tsx b/packages/desktop-client/src/components/reports/ReportSummary.tsx index e30ce2129b..66557eee68 100644 --- a/packages/desktop-client/src/components/reports/ReportSummary.tsx +++ b/packages/desktop-client/src/components/reports/ReportSummary.tsx @@ -169,7 +169,14 @@ export function ReportSummary({ - Per {(ReportOptions.intervalMap.get(interval) || '').toLowerCase()} + + Per{' '} + {{ + interval: ( + ReportOptions.intervalMap.get(interval) || '' + ).toLowerCase(), + }} + diff --git a/packages/desktop-client/src/components/reports/graphs/CashFlowGraph.tsx b/packages/desktop-client/src/components/reports/graphs/CashFlowGraph.tsx index a73c44f975..0385a8a615 100644 --- a/packages/desktop-client/src/components/reports/graphs/CashFlowGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/CashFlowGraph.tsx @@ -139,7 +139,7 @@ export function CashFlowGraph({ dataKey="date" tick={{ fill: theme.reportsLabel }} tickFormatter={x => { - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography return d.format(x, isConcise ? "MMM ''yy" : 'MMM d', { locale, }); @@ -159,7 +159,7 @@ export function CashFlowGraph({ /> { - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography return d.format(x, isConcise ? "MMM ''yy" : 'MMM d', { locale, }); diff --git a/packages/desktop-client/src/components/reports/reports/Summary.tsx b/packages/desktop-client/src/components/reports/reports/Summary.tsx index beeccd2cf9..d1382bb7dc 100644 --- a/packages/desktop-client/src/components/reports/reports/Summary.tsx +++ b/packages/desktop-client/src/components/reports/reports/Summary.tsx @@ -326,7 +326,9 @@ function SummaryInner({ widget }: SummaryInnerProps) { padding: 16, }} > - {t('Show as')} + + Show as + - + ); diff --git a/packages/desktop-client/src/components/select/RecurringSchedulePicker.tsx b/packages/desktop-client/src/components/select/RecurringSchedulePicker.tsx index 6326a03108..c2047b1f6f 100644 --- a/packages/desktop-client/src/components/select/RecurringSchedulePicker.tsx +++ b/packages/desktop-client/src/components/select/RecurringSchedulePicker.tsx @@ -484,7 +484,9 @@ function RecurringScheduleTooltip({ style={{ marginTop: 10 }} spacing={1} > - {t('Repeat every')} + + Repeat every + dispatch({ type: 'add-recurrence' })} > - {t('Add specific days')} + Add specific days ) : null} diff --git a/packages/desktop-client/src/components/settings/Export.tsx b/packages/desktop-client/src/components/settings/Export.tsx index 42f39610f7..c87e69c530 100644 --- a/packages/desktop-client/src/components/settings/Export.tsx +++ b/packages/desktop-client/src/components/settings/Export.tsx @@ -48,7 +48,7 @@ export function ExportBudget() { primaryAction={ <> - {t('Export data')} + Export data {error && ( diff --git a/packages/desktop-client/src/hooks/useFormat.ts b/packages/desktop-client/src/hooks/useFormat.ts index a28872a115..d8a1e394f6 100644 --- a/packages/desktop-client/src/hooks/useFormat.ts +++ b/packages/desktop-client/src/hooks/useFormat.ts @@ -47,7 +47,7 @@ function format( switch (type) { case 'string': { const val = JSON.stringify(value); - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography if (val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') { return { formattedString: val.slice(1, -1) }; } diff --git a/packages/eslint-plugin-actual/lib/index.js b/packages/eslint-plugin-actual/lib/index.js new file mode 100644 index 0000000000..2d10052aeb --- /dev/null +++ b/packages/eslint-plugin-actual/lib/index.js @@ -0,0 +1,8 @@ +module.exports = { + rules: { + 'no-untranslated-strings': require('./rules/no-untranslated-strings'), + 'prefer-trans-over-t': require('./rules/prefer-trans-over-t'), + typography: require('./rules/typography'), + 'prefer-if-statement': require('./rules/prefer-if-statement'), + }, +}; diff --git a/packages/eslint-plugin-actual/lib/rules/no-untranslated-strings.js b/packages/eslint-plugin-actual/lib/rules/no-untranslated-strings.js new file mode 100644 index 0000000000..db2ec376b6 --- /dev/null +++ b/packages/eslint-plugin-actual/lib/rules/no-untranslated-strings.js @@ -0,0 +1,89 @@ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Disallow non-translated English strings', + recommended: true, + }, + schema: [], + messages: { + noHardcoded: 'Non-translated English string. Wrap in .', + }, + }, + create(context) { + const whitelist = [ + 'Actual', + 'GoCardless', + 'SimpleFIN', + 'Pluggy.ai', + 'YNAB', + 'nYNAB', + 'YNAB4', + + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + ]; + + function isProbablyEnglish(text) { + const trimmed = text.trim(); + if (whitelist.includes(trimmed)) { + return false; + } + + // very basic - but it'll catch most cases + return /^[A-Z][a-z].*[a-z](\p{P})?$/.test(trimmed); + } + + function isInsideTrans(node) { + let parent = node.parent; + while (parent) { + if (parent.type === 'JSXElement') { + const elementName = parent.openingElement.name; + // Check for both JSXIdentifier and JSXMemberExpression (e.g., Trans.Provider) + if ( + elementName.type === 'JSXIdentifier' && + elementName.name === 'Trans' + ) { + return true; + } + if ( + elementName.type === 'JSXMemberExpression' && + elementName.object.name === 'Trans' + ) { + return true; + } + } + parent = parent.parent; + } + return false; + } + + return { + JSXText(node) { + if (isProbablyEnglish(node.value) && !isInsideTrans(node)) { + context.report({ node, messageId: 'noHardcoded' }); + } + }, + Literal(node) { + if ( + node.parent && + node.parent.type === 'JSXExpressionContainer' && + typeof node.value === 'string' && + isProbablyEnglish(node.value) && + !isInsideTrans(node) + ) { + context.report({ node, messageId: 'noHardcoded' }); + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin-actual/lib/rules/prefer-trans-over-t.js b/packages/eslint-plugin-actual/lib/rules/prefer-trans-over-t.js new file mode 100644 index 0000000000..ef345b6b00 --- /dev/null +++ b/packages/eslint-plugin-actual/lib/rules/prefer-trans-over-t.js @@ -0,0 +1,153 @@ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'Prefer over t() for simple text content in JSX', + recommended: true, + }, + schema: [], + messages: { + // eslint-disable-next-line actual/typography + preferTrans: "Prefer {{ text }} over t('{{ text }}')", + }, + fixable: 'code', + }, + create(context) { + const simpleTextPattern = /^[A-Z][a-z\s]*[a-z](\p{P})?$/u; + + function isSimpleText(text) { + const trimmed = text.trim(); + if (trimmed === '') return false; + + // Skip if it contains interpolation or complex patterns + if (trimmed.includes('{{') || trimmed.includes('}}')) return false; + if (trimmed.includes('\\') || trimmed.includes('`')) return false; + + return simpleTextPattern.test(trimmed); + } + + function isJSXContent(node) { + let parent = node.parent; + + // We don't want to outlaw t() calls in these contexts + const disallowedTypes = [ + 'AssignmentExpression', // title = t('Text') + 'VariableDeclarator', // const title = t('Text') + 'JSXAttribute', // title={t('Text')} + 'JSXOpeningElement', //
    + 'ConditionalExpression', // condition ? t('Yes') : t('No') + 'LogicalExpression', // condition && t('Text') + 'CallExpression', // someFunction(t('Text')) + 'Property', // { title: t('Text') } + ]; + + while (parent) { + if (disallowedTypes.includes(parent.type)) { + return false; + } + + if (parent.type === 'ReturnStatement') { + // Checks if return statement is inside JSX context + // return
    {t('Text')}
    - WILL flag + // return t('Text') - will NOT flag + let returnParent = parent.parent; + while (returnParent) { + if (returnParent.type === 'JSXElement') { + break; + } + if ( + returnParent.type === 'FunctionDeclaration' || + returnParent.type === 'FunctionExpression' || + returnParent.type === 'ArrowFunctionExpression' + ) { + return false; + } + returnParent = returnParent.parent; + } + } + + if (parent.type === 'JSXExpressionContainer') { + // Checks if expression container is inside JSX children (not attributes) + //
    {t('Text')}
    - WILL flag + //
    - will NOT flag + let containerParent = parent.parent; + while (containerParent) { + if (containerParent.type === 'JSXAttribute') { + return false; + } + if (containerParent.type === 'JSXOpeningElement') { + return false; + } + if (containerParent.type === 'JSXElement') { + const children = containerParent.children || []; + return children.some(child => child === parent || child === node); + } + containerParent = containerParent.parent; + } + return true; + } + + if (parent.type === 'JSXElement') { + //
    {t('Text')}
    - WILL flag + //
    - will NOT flag + const children = parent.children || []; + return children.some( + child => + child === node || + (child.type === 'JSXExpressionContainer' && + child.expression === node), + ); + } + + parent = parent.parent; + } + + return false; + } + + return { + CallExpression(node) { + if ( + node.callee.type === 'Identifier' && + node.callee.name === 't' && + node.arguments.length === 1 && + node.arguments[0].type === 'Literal' && + typeof node.arguments[0].value === 'string' && + isSimpleText(node.arguments[0].value) && + isJSXContent(node) + ) { + context.report({ + node, + messageId: 'preferTrans', + data: { + text: node.arguments[0].value, + }, + fix(fixer) { + const text = node.arguments[0].value; + const sourceCode = context.getSourceCode(); + const program = sourceCode.ast; + + const fixes = [fixer.replaceText(node, `${text}`)]; + + const firstImport = program.body.find( + n => n.type === 'ImportDeclaration', + ); + if (firstImport) { + fixes.unshift( + fixer.insertTextAfter( + firstImport, + // eslint-disable-next-line actual/typography + "\nimport { Trans } from 'react-i18next';", + ), + ); + } + + return fixes; + }, + }); + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin-actual/lib/rules/typography.js b/packages/eslint-plugin-actual/lib/rules/typography.js index 2eb4cf866d..e05e084bb2 100644 --- a/packages/eslint-plugin-actual/lib/rules/typography.js +++ b/packages/eslint-plugin-actual/lib/rules/typography.js @@ -13,7 +13,7 @@ module.exports = { fixable: null, schema: [], messages: { - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography quote: `Avoid using straight quotes (' or ") in user-visible text. Use curly quotes (‘ ’ or “ ”) instead.`, }, }, @@ -26,7 +26,7 @@ module.exports = { //---------------------------------------------------------------------- function check(node, { value = node.value, strip = false } = {}) { - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography if (!value.includes("'") && !value.includes('"')) return; let rawText = context.getSourceCode().getText(node); diff --git a/packages/loot-core/migrations/1722804019000_create_dashboard_table.js b/packages/loot-core/migrations/1722804019000_create_dashboard_table.js index fbed322fce..7ccbafb982 100644 --- a/packages/loot-core/migrations/1722804019000_create_dashboard_table.js +++ b/packages/loot-core/migrations/1722804019000_create_dashboard_table.js @@ -1,6 +1,6 @@ import { v4 as uuidv4 } from 'uuid'; -/* eslint-disable rulesdir/typography */ +/* eslint-disable actual/typography */ export default async function runMigration(db) { db.transaction(() => { db.execQuery(` diff --git a/packages/loot-core/src/server/aql/compiler.ts b/packages/loot-core/src/server/aql/compiler.ts index d35477b47f..5dda25fa37 100644 --- a/packages/loot-core/src/server/aql/compiler.ts +++ b/packages/loot-core/src/server/aql/compiler.ts @@ -38,7 +38,7 @@ function isKeyword(str) { } export function quoteAlias(alias) { - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography return alias.indexOf('.') === -1 && !isKeyword(alias) ? alias : `"${alias}"`; } @@ -342,7 +342,7 @@ function val(state, expr, type?: string) { } if (castedExpr.literal) { - /* eslint-disable rulesdir/typography */ + /* eslint-disable actual/typography */ if (castedExpr.type === 'id') { return `'${castedExpr.value}'`; } else if (castedExpr.type === 'string') { @@ -350,7 +350,7 @@ function val(state, expr, type?: string) { const value = castedExpr.value.replace(/'/g, "''"); return `'${value}'`; } - /* eslint-enable rulesdir/typography */ + /* eslint-enable actual/typography */ } return castedExpr.value; @@ -718,7 +718,7 @@ const compileOp = saveStack('op', (state, fieldRef, opData) => { const [left, right] = valArray(state, [lhs, rhs], [null, 'array']); // Dedupe the ids const ids = [...new Set(right)]; - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography return `${left} IN (` + ids.map(id => `'${id}'`).join(',') + ')'; } case '$like': { diff --git a/packages/loot-core/src/server/aql/schema/index.ts b/packages/loot-core/src/server/aql/schema/index.ts index 18f1239081..606116af55 100644 --- a/packages/loot-core/src/server/aql/schema/index.ts +++ b/packages/loot-core/src/server/aql/schema/index.ts @@ -304,7 +304,7 @@ export const schemaConfig: SchemaConfig = { schedules: { v_schedules: internalFields => { - /* eslint-disable rulesdir/typography */ + /* eslint-disable actual/typography */ const fields = internalFields({ next_date: ` CASE @@ -328,7 +328,7 @@ export const schemaConfig: SchemaConfig = { LEFT JOIN rules _rules ON _rules.id = _.rule LEFT JOIN payee_mapping pm ON pm.id = json_extract(_rules.conditions, _paths.payee || '.value') `; - /* eslint-enable rulesdir/typography */ + /* eslint-enable actual/typography */ }, }, diff --git a/packages/loot-core/src/server/budget/statements.ts b/packages/loot-core/src/server/budget/statements.ts index 4547c5cdc2..b2658578f2 100644 --- a/packages/loot-core/src/server/budget/statements.ts +++ b/packages/loot-core/src/server/budget/statements.ts @@ -3,7 +3,7 @@ import { DbSchedule } from '../db'; import { GOAL_PREFIX, TEMPLATE_PREFIX } from './template-notes'; -/* eslint-disable rulesdir/typography */ +/* eslint-disable actual/typography */ export async function resetCategoryGoalDefsWithNoTemplates(): Promise { await db.run( ` @@ -17,7 +17,7 @@ export async function resetCategoryGoalDefsWithNoTemplates(): Promise { ); } -/* eslint-enable rulesdir/typography */ +/* eslint-enable actual/typography */ export type CategoryWithTemplateNote = { id: string; diff --git a/packages/loot-core/src/server/db/index.ts b/packages/loot-core/src/server/db/index.ts index 0d44b1988e..d5f3fccdda 100644 --- a/packages/loot-core/src/server/db/index.ts +++ b/packages/loot-core/src/server/db/index.ts @@ -662,7 +662,7 @@ export function getCommonPayees() { `); } -/* eslint-disable rulesdir/typography */ +/* eslint-disable actual/typography */ const orphanedPayeesQuery = ` SELECT p.id FROM payees p @@ -680,7 +680,7 @@ const orphanedPayeesQuery = ` AND json_extract(cond.value, '$.value') = pm.targetId ); `; -/* eslint-enable rulesdir/typography */ +/* eslint-enable actual/typography */ export function syncGetOrphanedPayees() { return all>(orphanedPayeesQuery); diff --git a/packages/loot-core/src/server/db/util.ts b/packages/loot-core/src/server/db/util.ts index c9814712e9..da601ef5cb 100644 --- a/packages/loot-core/src/server/db/util.ts +++ b/packages/loot-core/src/server/db/util.ts @@ -30,7 +30,7 @@ export async function incrFetch( export function whereIn(ids: string[], field: string) { const ids2 = [...new Set(ids)]; - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography const filter = `${field} IN (` + ids2.map(id => `'${id}'`).join(',') + ')'; return filter; } diff --git a/packages/loot-core/src/server/transactions/import/ofx2json.ts b/packages/loot-core/src/server/transactions/import/ofx2json.ts index d93b8929ce..03b127d818 100644 --- a/packages/loot-core/src/server/transactions/import/ofx2json.ts +++ b/packages/loot-core/src/server/transactions/import/ofx2json.ts @@ -33,8 +33,8 @@ export function html2Plain(value) { return value ?.replace(/</g, '<') // lessthan .replace(/>/g, '>') // greaterthan - .replace(/'/g, "'") // eslint-disable-line rulesdir/typography - .replace(/"/g, '"') // eslint-disable-line rulesdir/typography + .replace(/'/g, "'") // eslint-disable-line actual/typography + .replace(/"/g, '"') // eslint-disable-line actual/typography .replace(/(&|&)/g, '&'); // ampersands } diff --git a/packages/loot-core/src/server/transactions/import/parse-file.ts b/packages/loot-core/src/server/transactions/import/parse-file.ts index d76fd0dd4e..d2d513f161 100644 --- a/packages/loot-core/src/server/transactions/import/parse-file.ts +++ b/packages/loot-core/src/server/transactions/import/parse-file.ts @@ -72,7 +72,7 @@ async function parseCSV( columns: options?.hasHeaderRow, bom: true, delimiter: options?.delimiter || ',', - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography quote: '"', trim: true, relax_column_count: true, diff --git a/packages/loot-core/src/server/transactions/index.ts b/packages/loot-core/src/server/transactions/index.ts index eda7a4b311..7ef4ab5d11 100644 --- a/packages/loot-core/src/server/transactions/index.ts +++ b/packages/loot-core/src/server/transactions/index.ts @@ -31,7 +31,7 @@ async function getTransactionsByIds( return incrFetch( (query, params) => db.selectWithSchema('transactions', query, params), ids, - // eslint-disable-next-line rulesdir/typography + // eslint-disable-next-line actual/typography id => `id = '${id}'`, where => `SELECT * FROM v_transactions_internal WHERE ${where}`, ); diff --git a/packages/sync-server/src/app-gocardless/banks/easybank_bawaatww.js b/packages/sync-server/src/app-gocardless/banks/easybank_bawaatww.js index 29d7ef3048..24c981ae09 100644 --- a/packages/sync-server/src/app-gocardless/banks/easybank_bawaatww.js +++ b/packages/sync-server/src/app-gocardless/banks/easybank_bawaatww.js @@ -1,5 +1,3 @@ -import * as d from 'date-fns'; - import { formatPayeeName } from '../../util/payee-name.js'; import { title } from '../../util/title/index.js'; diff --git a/packages/sync-server/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js b/packages/sync-server/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js index b84452ec7c..dadae97515 100644 --- a/packages/sync-server/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +++ b/packages/sync-server/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js @@ -1,5 +1,3 @@ -import { formatPayeeName } from '../../util/payee-name.js'; - import Fallback from './integration-bank.js'; /** @type {import('./bank.interface.js').IBank} */ diff --git a/upcoming-release-notes/5212.md b/upcoming-release-notes/5212.md new file mode 100644 index 0000000000..0cf0135a7e --- /dev/null +++ b/upcoming-release-notes/5212.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [matt-fidd] +--- + +Introduce linting rules to improve translation consistency and coverage diff --git a/yarn.lock b/yarn.lock index 84b2608e07..d1c73c7d44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7623,7 +7623,6 @@ __metadata: eslint-plugin-jsx-a11y: "npm:^6.10.2" eslint-plugin-react: "npm:^7.37.5" eslint-plugin-react-hooks: "npm:^5.2.0" - eslint-plugin-rulesdir: "npm:^0.2.2" eslint-plugin-typescript-paths: "npm:^0.0.33" globals: "npm:^15.15.0" html-to-image: "npm:^1.11.13" @@ -10959,13 +10958,6 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-rulesdir@npm:^0.2.2": - version: 0.2.2 - resolution: "eslint-plugin-rulesdir@npm:0.2.2" - checksum: 10/aac282554e5eb5b1fb3944dd43a08be5fd3e0bc33a00738f525df08344bc1d54f4abe3303118582d86776b3146ddb9e09d3fd1af502d484b3354da2bfee2ce24 - languageName: node - linkType: hard - "eslint-plugin-typescript-paths@npm:^0.0.33": version: 0.0.33 resolution: "eslint-plugin-typescript-paths@npm:0.0.33"