Ga naar hoofdinhoud

Internationalisation (i18n)

How the library handles strings

@conduction/nextcloud-vue ships its own translation bundles (l10n/en.json, l10n/nl.json) and registers them under the nextcloud-vue namespace at runtime. Every user-facing string inside the library is resolved through t('nextcloud-vue', '...') with an English default, so:

  • Components work out of the box in the user's current Nextcloud language (for shipped locales).
  • Your app does not need to re-translate library strings.
  • Every translatable prop is still overridable — pass a pre-translated string and the library prop wins.
Required bootstrap call

For the shipped translations to take effect, consumers MUST call registerTranslations() once during app bootstrap — see Getting Started. Without it, every library string falls back to English even when the user's Nextcloud language is Dutch.

// main.js
import { registerTranslations } from '@conduction/nextcloud-vue'

registerTranslations()

registerTranslations():

  • Reads the current language via getLanguage() from @nextcloud/l10n.
  • Picks the matching bundle (primary subtag match, falls back to en).
  • Calls register('nextcloud-vue', bundle) from @nextcloud/l10n.

Setting up i18n for your own app strings

The section above covers the library's own strings. Your app's strings (page titles, custom labels, anything you pass into props) still need your own translation setup.

Nextcloud apps use @nextcloud/l10n for translations. It reads Nextcloud's built-in gettext catalog (.po files compiled to JSON) and provides a t() function.

1. Install the package

npm install @nextcloud/l10n

2. Create translation files

Nextcloud looks for l10n/{locale}.json files in your app root. Create at minimum en.json and nl.json:

l10n/en.json

{
"translations": {
"Clients": "Clients",
"Add Client": "Add Client",
"Delete Client": "Delete Client",
"Are you sure you want to permanently delete \"{name}\"?": "Are you sure you want to permanently delete \"{name}\"?",
"Client successfully deleted.": "Client successfully deleted.",
"No clients found": "No clients found",
"Items per page:": "Items per page:",
"Page {current} of {total}": "Page {current} of {total}"
},
"plurals": ""
}

l10n/nl.json

{
"translations": {
"Clients": "Klanten",
"Add Client": "Klant toevoegen",
"Delete Client": "Klant verwijderen",
"Are you sure you want to permanently delete \"{name}\"?": "Weet je zeker dat je \"{name}\" permanent wilt verwijderen?",
"Client successfully deleted.": "Klant succesvol verwijderd.",
"No clients found": "Geen klanten gevonden",
"Items per page:": "Items per pagina:",
"Page {current} of {total}": "Pagina {current} van {total}"
},
"plurals": ""
}

Nextcloud automatically selects the correct file based on the user's language preference.

3. Import the translate function

// In each Vue component or in main.js
import { translate as t, translatePlural as n } from '@nextcloud/l10n'

For Vue 2 components, register t globally so all templates can use it:

// main.js
import Vue from 'vue'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'

Vue.prototype.t = t
Vue.prototype.n = n

Overriding a library string

The shipped bundles cover the default labels. To change wording for a specific prop in your app — say a domain-specific verb instead of "Delete" — pass a pre-translated prop and it will override the library's default:

<template>
<CnIndexPage
:title="t('myapp', 'Clients')"
:description="t('myapp', 'Manage your client relationships')"
:empty-text="t('myapp', 'No clients found')"
:add-label="t('myapp', 'Add Client')"
@create="onCreate"
@delete="onDelete">

<!-- Translate the delete dialog -->
<template #delete-dialog="{ item, close }">
<CnDeleteDialog
:dialog-title="t('myapp', 'Delete Client')"
:warning-text="t('myapp', 'Are you sure you want to permanently delete \"{name}\"?').replace('{name}', item.name)"
:success-text="t('myapp', 'Client successfully deleted.')"
:cancel-label="t('myapp', 'Cancel')"
:confirm-label="t('myapp', 'Delete')"
:item="item"
@close="close"
@confirm="onDelete(item.id)" />
</template>
</CnIndexPage>
</template>

For apps with many components, create a central translation helper so you don't repeat t('myapp', ...) everywhere:

// src/i18n.js
import { translate as t, translatePlural as n } from '@nextcloud/l10n'

const APP_NAME = 'myapp'

export const i18n = {
// Navigation
clients: () => t(APP_NAME, 'Clients'),
contacts: () => t(APP_NAME, 'Contacts'),

// Actions
add: (entity) => t(APP_NAME, 'Add {entity}').replace('{entity}', entity),
delete: (entity) => t(APP_NAME, 'Delete {entity}').replace('{entity}', entity),
edit: (entity) => t(APP_NAME, 'Edit {entity}').replace('{entity}', entity),
cancel: () => t(APP_NAME, 'Cancel'),
save: () => t(APP_NAME, 'Save'),

// Confirmations
deleteWarning: (name) =>
t(APP_NAME, 'Are you sure you want to permanently delete "{name}"? This action cannot be undone.')
.replace('{name}', name),
deleteSuccess: (entity) => t(APP_NAME, '{entity} successfully deleted.').replace('{entity}', entity),

// Table
noItems: (entity) => t(APP_NAME, 'No {entity} found').replace('{entity}', entity),
itemsPerPage: () => t(APP_NAME, 'Items per page:'),
pageInfo: (current, total) =>
t(APP_NAME, 'Page {current} of {total}')
.replace('{current}', current)
.replace('{total}', total),
}

Then in your components:

<script>
import { i18n } from '../../i18n.js'

export default {
computed: {
strings() {
return {
title: i18n.clients(),
addLabel: i18n.add('Client'),
emptyText: i18n.noItems('clients'),
}
},
},
}
</script>

Adding a new language

  1. Add l10n/<lang>.json in this repo using the standard Nextcloud format ({"translations": { ... }, "plurals": "" }).
  2. Import the new bundle and add it to the BUNDLES map in src/l10n/index.js.
  3. Run npm run build — the bundle is inlined into dist/.

Complete props reference by component

Every library string is backed by one of the props below. The shipped bundles cover the default values; override a prop if you need different wording (see "Overriding a library string" above).

CnIndexPage

PropEnglish DefaultNotes
title(required)Page heading
description''Subheading
emptyText'No items found'Empty state message
addLabel'Add {schema.title}'Add button label

CnDeleteDialog

PropEnglish DefaultNotes
dialogTitle'Delete Item'Dialog heading
warningText'Are you sure…'Supports {name} placeholder
successText'Item successfully deleted.'Phase-2 success message
cancelLabel'Cancel'
confirmLabel'Delete'
closeLabel'Close'Phase-2 close button

CnCopyDialog

PropEnglish DefaultNotes
dialogTitle'Copy Item'
patternLabel'Naming pattern'
successText'Item successfully copied.'
cancelLabel'Cancel'
confirmLabel'Copy'
closeLabel'Close'

CnFormDialog

PropEnglish DefaultNotes
dialogTitle'Create {title}' / 'Edit {title}'Auto-derived from schema
successText'Item saved successfully.'
cancelLabel'Cancel'
confirmLabel'Create' / 'Save'Auto-derived from mode
closeLabel'Close'

CnMassDeleteDialog / CnMassCopyDialog

PropEnglish Default
cancelLabel'Cancel'
confirmLabel'Delete' / 'Copy'
closeLabel'Close'
removeLabel'Remove from list'

CnMassExportDialog

PropEnglish Default
formatLabel'Export format'
cancelLabel'Cancel'
confirmLabel'Export'
closeLabel'Close'

CnMassImportDialog

PropEnglish Default
supportedFormatsLabel'Supported file types:'
selectFileLabel'Select File'
sheetLabel'Sheet'
foundLabel'Found'
createdLabel'Created'
cancelLabel'Cancel'
confirmLabel'Import'
closeLabel'Close'

CnMassActionBar

PropEnglish DefaultNotes
menuLabelTemplate'Mass Actions ({count})'{count} is replaced
importLabel'Import'
exportLabel'Export'
copyLabel'Copy'
deleteLabel'Delete'

CnPagination

PropEnglish DefaultNotes
firstLabel'First'
previousLabel'Previous'
nextLabel'Next'
lastLabel'Last'
itemsPerPageLabel'Items per page:'
pageInfoFormat'Page {current} of {total}'{current} and {total} replaced

CnDataTable

PropEnglish Default
emptyText'No items found'
loadingText'Loading...'

CnFilterBar / CnIndexSidebar

PropEnglish Default
searchPlaceholder'Search...' / 'Type to search...'
clearAllLabel'Clear filters'
searchTabLabel'Search'
columnsTabLabel'Columns'
searchLabel'Search'
filtersLabel'Filters'
columnsHeading'Column Visibility'
columnsDescription'Select which columns to display in the table'

Schema-driven labels

Column headers and form field labels are auto-generated from the schema. Override per-field via:

// columnOverrides — override table column labels
columnOverrides: {
clientType: { label: t('myapp', 'Client Type') },
createdAt: { label: t('myapp', 'Date Added') },
}

// fieldOverrides — override form field labels
fieldOverrides: {
clientType: { label: t('myapp', 'Client Type'), placeholder: t('myapp', 'Select a type…') },
}

Row action labels come from your actions array:

actions: [
{ label: t('myapp', 'Edit'), icon: 'Pencil', handler: 'edit' },
{ label: t('myapp', 'Delete'), icon: 'Delete', handler: 'delete', destructive: true },
]