fix(invoices): resolve ROT/RUT strings and hide the payout action when it does not apply (#1429)

The ROT/RUT payout feature shipped in #1380 with all 44 of its strings
written into the invoice_editor namespace, while RotRutPayoutDialog and
the invoices page both read useTranslations('invoices'). next-intl falls
back to rendering the key path, so production showed literal
"invoices.rot_rut_payout_title" text where every label should be, and the
header button read "invoices.rot_rut_payout_action".

Move the keys to the invoices namespace, textually rather than through a
JSON round trip: the message files contain duplicate keys that
JSON.parse/stringify would silently drop.

Three other dialogs had the same namespace mismatch and are fixed the
same way, by adding the strings they reference to the namespace they read
from (TemplateBookDialog reads bookkeeping, Correction/StrikeLines read
journal_detail).

Add i18n/__tests__/message-keys.test.ts, which resolves every literal t()
key in app/, components/ and extensions/ against both locales. next-intl
has no build-time check and degrades by rendering the key path, so this
class of bug reaches users silently otherwise.

Also gate the payout action. Begäran om utbetalning (Lag 2009:194 8 §)
only concerns companies selling ROT/RUT-eligible work to consumers, so
the header button now appears only when the company has invoiced a
deduction or has opted into ROT/RUT in tax settings. The flag comes from
the company_settings row the page already fetches for ore_rounding, so
there is no extra request. ?rot-rut=1 still opens the dialog.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-06 11:13:25 +02:00
committed by GitHub
parent ab712f829a
commit 902b3ee986
5 changed files with 237 additions and 101 deletions
+2
View File
@@ -799,3 +799,5 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-08-05] Dropped "ML 13 kap 8 §" cites for kontantmetoden VAT timing (comments/docs only): section is the old ML 1994:200 numbering; in ML 2023:200, 13 kap is input-VAT deduction. Rule stated without section cite until the current-law section is verified.
[2026-08-05] The in-app assistant now reads invoice_inbox_items.channel_context (the answers a user gave in WhatsApp) as first-class underlag context, marked "uppgivna av användaren" and ranked above OCR output. Found in the field: the assistant asked for representation participants the user had typed into WhatsApp minutes earlier, because the intent's inbox query selected only document_id + extracted_data. Also backfilled by document_id, since a receipt can reach the intent through the document paths without the inbox row being matched to the transaction.
[2026-08-05] The WhatsApp representation question now asks ONCE for a missing purpose instead of silently storing participants with purpose=null. Skatteverket wants participants AND purpose; accepting half and saying "Tack!" produced an undocumented deduction. Anti-loop: the follow-up fires only when no representation block exists yet, so a second incomplete answer is accepted as-is rather than nagging.
[2026-08-06] ROT/RUT payout strings were placed in the invoice_editor namespace while RotRutPayoutDialog and the invoices page read useTranslations('invoices'), so all 44 labels rendered as raw "invoices.rot_rut_*" key paths in production since #1380. Moved the keys to invoices rather than repointing the components, since the dialog belongs to the invoice list, not the editor. Message files are edited textually, never via JSON.parse/stringify: they contain duplicate keys a round trip would silently drop. Same bug class fixed in TemplateBookDialog (bookkeeping) and Correction/StrikeLines dialogs (journal_detail) by adding the strings to the namespace each component reads, matching the existing precedent that toast_posted_* is duplicated across journal_list and journal_detail. Added i18n/__tests__/message-keys.test.ts, which resolves every literal t() key against both locales: next-intl has no build-time check and fails by rendering the key path, so nothing caught this before users did.
[2026-08-06] The ROT/RUT payout button is hidden from the invoices header unless the company has an invoice with deduction_total > 0 or rot_rut_enabled is on in tax settings. ROT/RUT concerns only companies selling eligible work to consumers, and a payout can never precede the invoice that created the claim, so the derived signal cannot hide the action from someone who needs it. Read from the company_settings row the page already fetches for ore_rounding (no extra round trip); deliberately not scoped to the fiscal-year filter, since a begäran is claimed the year after payment. ?rot-rut=1 still opens the dialog, so the feature is hidden, not removed.
+29 -11
View File
@@ -154,6 +154,7 @@ export default function InvoicesPage() {
const searchParams = useSearchParams()
const [invoices, setInvoices] = useState<Invoice[]>([])
const [oreRounding, setOreRounding] = useState<boolean>(true)
const [rotRutEnabled, setRotRutEnabled] = useState<boolean>(false)
const [isLoading, setIsLoading] = useState(true)
const [searchTerm, setSearchTerm] = useState('')
const [sort, setSort] = useState<InvoiceListSort | null>(null)
@@ -192,6 +193,16 @@ export default function InvoicesPage() {
const closeRotRutPayout = () => router.replace('/invoices', { scroll: false })
const openRotRutPayout = () => router.push('/invoices?rot-rut=1', { scroll: false })
// Begäran om utbetalning (Lag 2009:194 8 §) only concerns companies selling
// ROT/RUT-eligible work to consumers, so the action stays out of the header
// for everyone else. It appears once the company has invoiced a deduction
// (the payout can never precede that invoice), or once ROT/RUT is opted into
// in tax settings. Not scoped to the fiscal-year filter: a payout is claimed
// the year after payment, so last year's invoices are exactly the relevant
// ones. ?rot-rut=1 keeps working regardless, so nothing is unreachable.
const showRotRutAction =
rotRutEnabled || invoices.some((invoice) => (invoice.deduction_total ?? 0) > 0)
async function fetchInvoices() {
if (!company) return
setIsLoading(true)
@@ -209,7 +220,7 @@ export default function InvoicesPage() {
),
supabase
.from('company_settings')
.select('ore_rounding')
.select('ore_rounding, rot_rut_enabled')
.eq('company_id', company.id)
.maybeSingle(),
])
@@ -228,6 +239,11 @@ export default function InvoicesPage() {
? (settingsResult.value.data?.ore_rounding ?? true)
: true,
)
setRotRutEnabled(
settingsResult.status === 'fulfilled'
? (settingsResult.value.data?.rot_rut_enabled ?? false)
: false,
)
setIsLoading(false)
}
@@ -382,16 +398,18 @@ export default function InvoicesPage() {
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<h1 className="font-display text-2xl leading-8 tracking-tight">{t('title')}</h1>
<div className="flex flex-wrap items-center gap-2">
<Button
type="button"
variant="outline"
onClick={openRotRutPayout}
disabled={!canWrite}
title={!canWrite ? t('viewer_disabled_tooltip') : undefined}
>
<FileDown className="mr-2 h-4 w-4" />
{t('rot_rut_payout_action')}
</Button>
{showRotRutAction && (
<Button
type="button"
variant="outline"
onClick={openRotRutPayout}
disabled={!canWrite}
title={!canWrite ? t('viewer_disabled_tooltip') : undefined}
>
<FileDown className="mr-2 h-4 w-4" />
{t('rot_rut_payout_action')}
</Button>
)}
<SplitButton
key={uiStateLoaded ? 'loaded' : 'initial'}
persistKey="invoices"
+102
View File
@@ -0,0 +1,102 @@
import { describe, it, expect } from 'vitest'
import fs from 'fs'
import path from 'path'
/**
* Guard against the failure mode that shipped the ROT/RUT payout dialog to
* production rendering raw key paths: the strings existed in messages/*.json,
* but under a different namespace than the component read from. next-intl has
* no build-time check for that, it silently renders "namespace.key", so every
* label in the feature turned into debug output for real users.
*
* This resolves every literal translation key against both locales, so a
* misplaced namespace fails the suite instead of the UI.
*/
const ROOT = path.resolve(__dirname, '../..')
const SCAN_DIRS = ['app', 'components', 'extensions']
const SKIP_DIRS = new Set(['node_modules', '.next', '.git', '.claude'])
function collectSourceFiles(dir: string, out: string[] = []): string[] {
if (!fs.existsSync(dir)) return out
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (SKIP_DIRS.has(entry.name)) continue
const full = path.join(dir, entry.name)
if (entry.isDirectory()) collectSourceFiles(full, out)
else if (/\.tsx?$/.test(entry.name) && !/\.test\.tsx?$/.test(entry.name)) out.push(full)
}
return out
}
/**
* Comments hold example code (`t('none_title')` in a JSDoc usage block) that
* never runs. Blank them out rather than reporting keys nobody renders.
*/
function stripComments(src: string): string {
return src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '')
}
function resolveKey(messages: unknown, dottedPath: string): unknown {
return dottedPath
.split('.')
.reduce<unknown>(
(node, part) =>
node && typeof node === 'object' ? (node as Record<string, unknown>)[part] : undefined,
messages,
)
}
interface Reference {
file: string
namespace: string
key: string
}
function collectReferences(): Reference[] {
const refs: Reference[] = []
for (const dir of SCAN_DIRS) {
for (const file of collectSourceFiles(path.join(ROOT, dir))) {
const src = stripComments(fs.readFileSync(file, 'utf8'))
// `const t = useTranslations('invoices')` / `= await getTranslations('x')`
const namespaces: Record<string, string> = {}
const declaration =
/(?:const|let)\s+(\w+)\s*=\s*(?:await\s+)?(?:useTranslations|getTranslations)\(\s*['"]([^'"]+)['"]\s*\)/g
for (const match of src.matchAll(declaration)) namespaces[match[1]] = match[2]
for (const [variable, namespace] of Object.entries(namespaces)) {
// Literal calls only: t(dynamicKey) cannot be checked statically.
const call = new RegExp(
`\\b${variable}(?:\\.rich|\\.markup|\\.raw)?\\(\\s*['"]([A-Za-z0-9_.]+)['"]`,
'g',
)
for (const match of src.matchAll(call)) {
refs.push({ file: path.relative(ROOT, file), namespace, key: match[1] })
}
}
}
}
return refs
}
describe('message keys', () => {
const references = collectReferences()
it('finds translation calls to check', () => {
expect(references.length).toBeGreaterThan(500)
})
for (const locale of ['sv', 'en'] as const) {
it(`resolves every referenced key in messages/${locale}.json`, () => {
const messages = JSON.parse(
fs.readFileSync(path.join(ROOT, 'messages', `${locale}.json`), 'utf8'),
)
const missing = references
.filter(({ namespace, key }) => resolveKey(messages, `${namespace}.${key}`) === undefined)
.map(({ file, namespace, key }) => `${file}: ${namespace}.${key}`)
expect([...new Set(missing)]).toEqual([])
})
}
})
+52 -45
View File
@@ -3203,51 +3203,7 @@
"to_pay_label": "Amount to pay",
"total_incl_vat_label": "Total incl. VAT",
"review_customer_missing_title": "Customer details could not be loaded",
"review_customer_missing_description": "Reload the page and try again. Contact support if the problem persists.",
"rot_rut_payout_action": "ROT/RUT file",
"rot_rut_payout_title": "Request a ROT/RUT payout",
"rot_rut_payout_description": "Select paid invoices and download an XML file for Skatteverket's e-service. The file is not submitted automatically: upload and sign it at Skatteverket.",
"rot_rut_type_aria": "Select deduction type",
"rot_rut_type_rot": "ROT",
"rot_rut_type_rut": "RUT",
"rot_rut_year_aria": "Select payment year",
"rot_rut_loading": "Loading ROT/RUT details",
"rot_rut_load_failed_title": "Could not load the ROT/RUT details",
"rot_rut_load_failed_description": "Reload the page and try again.",
"rot_rut_eligible_title": "Invoices to include",
"rot_rut_selected_count": "{selected} selected · {amount}",
"rot_rut_select_all": "Select all",
"rot_rut_clear_selection": "Clear selection",
"rot_rut_no_eligible_title": "No invoices are ready",
"rot_rut_no_eligible_description": "A paid ROT or RUT invoice appears here once its buyer details are complete.",
"rot_rut_paid_at": "Paid {date}",
"rot_rut_max_cases_help": "Skatteverket allows at most {count} cases in one file. Create multiple files if you need to request more invoices.",
"rot_rut_generate_file": "Create and download file",
"rot_rut_generating_file": "Creating file…",
"rot_rut_generated_title": "The ROT/RUT file was downloaded",
"rot_rut_generated_description": "Upload the file in Skatteverket's e-service and sign the request there.",
"rot_rut_generate_failed_title": "Could not create the ROT/RUT file",
"rot_rut_blocked_title": "Cannot be included ({count})",
"rot_rut_history_title": "Previous files",
"rot_rut_upload_help": "After uploading and signing at Skatteverket, mark the file as uploaded here.",
"rot_rut_history_empty": "No files have been created for this deduction type.",
"rot_rut_history_meta": "{date} · {count} cases · {amount}",
"rot_rut_status_generated": "Created",
"rot_rut_status_submitted": "Uploaded",
"rot_rut_status_paid": "Approved",
"rot_rut_status_partially_paid": "Partly approved",
"rot_rut_status_rejected": "Rejected",
"rot_rut_status_cancelled": "Cancelled",
"rot_rut_download_again": "Download again",
"rot_rut_cancel_request": "Cancel",
"rot_rut_mark_uploaded": "Mark as uploaded",
"rot_rut_uploaded_title": "The file is marked as uploaded",
"rot_rut_cancelled_title": "The request was cancelled",
"rot_rut_update_failed_title": "Could not update the request",
"rot_rut_download_failed_title": "Could not download the file",
"rot_rut_download_timeout": "The download took too long. Try again.",
"rot_rut_download_network": "The file could not be downloaded. Check your connection and try again.",
"rot_rut_skatteverket_link": "Open ROT and RUT at Skatteverket"
"review_customer_missing_description": "Reload the page and try again. Contact support if the problem persists."
},
"invoice_review": {
"assigned_number_prefix": "Will be assigned invoice number",
@@ -4345,6 +4301,9 @@
"current": "Current"
},
"journal_detail": {
"accounts_loading": "Loading chart of accounts...",
"accounts_load_failed": "The chart of accounts could not be loaded. Try again to change the account.",
"accounts_retry": "Try again",
"edit_draft": "Edit",
"back": "Back to bookkeeping",
"loading": "Loading journal entry...",
@@ -5047,6 +5006,10 @@
"footer_to_handle": "{count, plural, =0 {Nothing to handle} =1 {1 to handle} other {# to handle}}"
},
"bookkeeping": {
"toast_post_failed": "Could not post",
"toast_posted_title": "Journal entry posted",
"toast_posted_description": "Journal entry {voucher} has been posted.",
"toast_post_failed_generic": "Could not post journal entry",
"edit_draft_dialog_title": "Edit draft",
"title": "Bookkeeping",
"year_end": "Year-end (Årsbokslut)",
@@ -5457,6 +5420,50 @@
"validation_min_one_row": "At least one row is required"
},
"invoices": {
"rot_rut_payout_action": "ROT/RUT file",
"rot_rut_payout_title": "Request a ROT/RUT payout",
"rot_rut_payout_description": "Select paid invoices and download an XML file for Skatteverket's e-service. The file is not submitted automatically: upload and sign it at Skatteverket.",
"rot_rut_type_aria": "Select deduction type",
"rot_rut_type_rot": "ROT",
"rot_rut_type_rut": "RUT",
"rot_rut_year_aria": "Select payment year",
"rot_rut_loading": "Loading ROT/RUT details",
"rot_rut_load_failed_title": "Could not load the ROT/RUT details",
"rot_rut_load_failed_description": "Reload the page and try again.",
"rot_rut_eligible_title": "Invoices to include",
"rot_rut_selected_count": "{selected} selected · {amount}",
"rot_rut_select_all": "Select all",
"rot_rut_clear_selection": "Clear selection",
"rot_rut_no_eligible_title": "No invoices are ready",
"rot_rut_no_eligible_description": "A paid ROT or RUT invoice appears here once its buyer details are complete.",
"rot_rut_paid_at": "Paid {date}",
"rot_rut_max_cases_help": "Skatteverket allows at most {count} cases in one file. Create multiple files if you need to request more invoices.",
"rot_rut_generate_file": "Create and download file",
"rot_rut_generating_file": "Creating file…",
"rot_rut_generated_title": "The ROT/RUT file was downloaded",
"rot_rut_generated_description": "Upload the file in Skatteverket's e-service and sign the request there.",
"rot_rut_generate_failed_title": "Could not create the ROT/RUT file",
"rot_rut_blocked_title": "Cannot be included ({count})",
"rot_rut_history_title": "Previous files",
"rot_rut_upload_help": "After uploading and signing at Skatteverket, mark the file as uploaded here.",
"rot_rut_history_empty": "No files have been created for this deduction type.",
"rot_rut_history_meta": "{date} · {count} cases · {amount}",
"rot_rut_status_generated": "Created",
"rot_rut_status_submitted": "Uploaded",
"rot_rut_status_paid": "Approved",
"rot_rut_status_partially_paid": "Partly approved",
"rot_rut_status_rejected": "Rejected",
"rot_rut_status_cancelled": "Cancelled",
"rot_rut_download_again": "Download again",
"rot_rut_cancel_request": "Cancel",
"rot_rut_mark_uploaded": "Mark as uploaded",
"rot_rut_uploaded_title": "The file is marked as uploaded",
"rot_rut_cancelled_title": "The request was cancelled",
"rot_rut_update_failed_title": "Could not update the request",
"rot_rut_download_failed_title": "Could not download the file",
"rot_rut_download_timeout": "The download took too long. Try again.",
"rot_rut_download_network": "The file could not be downloaded. Check your connection and try again.",
"rot_rut_skatteverket_link": "Open ROT and RUT at Skatteverket",
"title": "Customer invoices",
"recurring": "Recurring",
"new_invoice": "New invoice",
+52 -45
View File
@@ -3203,51 +3203,7 @@
"to_pay_label": "Att betala",
"total_incl_vat_label": "Totalt inkl. moms",
"review_customer_missing_title": "Kunduppgifterna kunde inte laddas",
"review_customer_missing_description": "Ladda om sidan och försök igen. Kontakta support om det inte hjälper.",
"rot_rut_payout_action": "ROT/RUT-fil",
"rot_rut_payout_title": "Begär utbetalning för ROT/RUT",
"rot_rut_payout_description": "Välj betalda fakturor och hämta en XML-fil för Skatteverkets e-tjänst. Filen skickas inte automatiskt: du laddar upp och signerar den hos Skatteverket.",
"rot_rut_type_aria": "Välj avdragstyp",
"rot_rut_type_rot": "ROT",
"rot_rut_type_rut": "RUT",
"rot_rut_year_aria": "Välj betalningsår",
"rot_rut_loading": "Laddar ROT/RUT-underlag",
"rot_rut_load_failed_title": "Kunde inte ladda ROT/RUT-underlaget",
"rot_rut_load_failed_description": "Ladda om sidan och försök igen.",
"rot_rut_eligible_title": "Fakturor att ta med",
"rot_rut_selected_count": "{selected} valda · {amount}",
"rot_rut_select_all": "Välj alla",
"rot_rut_clear_selection": "Rensa val",
"rot_rut_no_eligible_title": "Inga fakturor är redo",
"rot_rut_no_eligible_description": "När en ROT- eller RUT-faktura är betald och har fullständiga köparuppgifter visas den här.",
"rot_rut_paid_at": "Betald {date}",
"rot_rut_max_cases_help": "Skatteverket tillåter högst {count} ärenden i samma fil. Skapa flera filer om fler fakturor ska begäras.",
"rot_rut_generate_file": "Skapa och hämta fil",
"rot_rut_generating_file": "Skapar fil…",
"rot_rut_generated_title": "ROT/RUT-filen är hämtad",
"rot_rut_generated_description": "Ladda upp filen i Skatteverkets e-tjänst och signera begäran där.",
"rot_rut_generate_failed_title": "Kunde inte skapa ROT/RUT-filen",
"rot_rut_blocked_title": "Kan inte tas med ({count})",
"rot_rut_history_title": "Tidigare filer",
"rot_rut_upload_help": "Efter uppladdning och signering hos Skatteverket markerar du filen som uppladdad här.",
"rot_rut_history_empty": "Inga filer har skapats för den här avdragstypen.",
"rot_rut_history_meta": "{date} · {count} ärenden · {amount}",
"rot_rut_status_generated": "Skapad",
"rot_rut_status_submitted": "Uppladdad",
"rot_rut_status_paid": "Beviljad",
"rot_rut_status_partially_paid": "Delvis beviljad",
"rot_rut_status_rejected": "Avslagen",
"rot_rut_status_cancelled": "Avbruten",
"rot_rut_download_again": "Hämta igen",
"rot_rut_cancel_request": "Avbryt",
"rot_rut_mark_uploaded": "Markera uppladdad",
"rot_rut_uploaded_title": "Filen är markerad som uppladdad",
"rot_rut_cancelled_title": "Begäran är avbruten",
"rot_rut_update_failed_title": "Kunde inte uppdatera begäran",
"rot_rut_download_failed_title": "Kunde inte hämta filen",
"rot_rut_download_timeout": "Hämtningen tog för lång tid. Försök igen.",
"rot_rut_download_network": "Filen kunde inte hämtas. Kontrollera anslutningen och försök igen.",
"rot_rut_skatteverket_link": "Öppna ROT och RUT hos Skatteverket"
"review_customer_missing_description": "Ladda om sidan och försök igen. Kontakta support om det inte hjälper."
},
"invoice_review": {
"assigned_number_prefix": "Tilldelas fakturanummer",
@@ -4345,6 +4301,9 @@
"current": "Aktuell"
},
"journal_detail": {
"accounts_loading": "Laddar kontoplan...",
"accounts_load_failed": "Kontoplanen kunde inte laddas. Försök igen för att ändra konto.",
"accounts_retry": "Försök igen",
"edit_draft": "Redigera",
"back": "Tillbaka till bokföring",
"loading": "Laddar verifikation...",
@@ -5047,6 +5006,10 @@
"footer_to_handle": "{count, plural, =0 {Inget att hantera} =1 {1 att hantera} other {# att hantera}}"
},
"bookkeeping": {
"toast_post_failed": "Kunde inte bokföra",
"toast_posted_title": "Verifikat bokfört",
"toast_posted_description": "Verifikat {voucher} har bokförts.",
"toast_post_failed_generic": "Kunde inte bokföra verifikat",
"edit_draft_dialog_title": "Redigera utkast",
"title": "Bokföring",
"year_end": "Årsbokslut",
@@ -5457,6 +5420,50 @@
"validation_min_one_row": "Minst en rad krävs"
},
"invoices": {
"rot_rut_payout_action": "ROT/RUT-fil",
"rot_rut_payout_title": "Begär utbetalning för ROT/RUT",
"rot_rut_payout_description": "Välj betalda fakturor och hämta en XML-fil för Skatteverkets e-tjänst. Filen skickas inte automatiskt: du laddar upp och signerar den hos Skatteverket.",
"rot_rut_type_aria": "Välj avdragstyp",
"rot_rut_type_rot": "ROT",
"rot_rut_type_rut": "RUT",
"rot_rut_year_aria": "Välj betalningsår",
"rot_rut_loading": "Laddar ROT/RUT-underlag",
"rot_rut_load_failed_title": "Kunde inte ladda ROT/RUT-underlaget",
"rot_rut_load_failed_description": "Ladda om sidan och försök igen.",
"rot_rut_eligible_title": "Fakturor att ta med",
"rot_rut_selected_count": "{selected} valda · {amount}",
"rot_rut_select_all": "Välj alla",
"rot_rut_clear_selection": "Rensa val",
"rot_rut_no_eligible_title": "Inga fakturor är redo",
"rot_rut_no_eligible_description": "När en ROT- eller RUT-faktura är betald och har fullständiga köparuppgifter visas den här.",
"rot_rut_paid_at": "Betald {date}",
"rot_rut_max_cases_help": "Skatteverket tillåter högst {count} ärenden i samma fil. Skapa flera filer om fler fakturor ska begäras.",
"rot_rut_generate_file": "Skapa och hämta fil",
"rot_rut_generating_file": "Skapar fil…",
"rot_rut_generated_title": "ROT/RUT-filen är hämtad",
"rot_rut_generated_description": "Ladda upp filen i Skatteverkets e-tjänst och signera begäran där.",
"rot_rut_generate_failed_title": "Kunde inte skapa ROT/RUT-filen",
"rot_rut_blocked_title": "Kan inte tas med ({count})",
"rot_rut_history_title": "Tidigare filer",
"rot_rut_upload_help": "Efter uppladdning och signering hos Skatteverket markerar du filen som uppladdad här.",
"rot_rut_history_empty": "Inga filer har skapats för den här avdragstypen.",
"rot_rut_history_meta": "{date} · {count} ärenden · {amount}",
"rot_rut_status_generated": "Skapad",
"rot_rut_status_submitted": "Uppladdad",
"rot_rut_status_paid": "Beviljad",
"rot_rut_status_partially_paid": "Delvis beviljad",
"rot_rut_status_rejected": "Avslagen",
"rot_rut_status_cancelled": "Avbruten",
"rot_rut_download_again": "Hämta igen",
"rot_rut_cancel_request": "Avbryt",
"rot_rut_mark_uploaded": "Markera uppladdad",
"rot_rut_uploaded_title": "Filen är markerad som uppladdad",
"rot_rut_cancelled_title": "Begäran är avbruten",
"rot_rut_update_failed_title": "Kunde inte uppdatera begäran",
"rot_rut_download_failed_title": "Kunde inte hämta filen",
"rot_rut_download_timeout": "Hämtningen tog för lång tid. Försök igen.",
"rot_rut_download_network": "Filen kunde inte hämtas. Kontrollera anslutningen och försök igen.",
"rot_rut_skatteverket_link": "Öppna ROT och RUT hos Skatteverket",
"title": "Kundfakturor",
"recurring": "Återkommande",
"new_invoice": "Ny faktura",