Skip to main content

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 },
]