feat: entry editor at /content/:key/new and /content/:key/:id
This commit is contained in:
1 parent
91d916fb74
commit
fba20f83c5
94 files changed
+7116
-423
No files matched your search
@@ -17,7 +17,7 @@ const SOURCES = [
|
||||
'components/SortSelect.vue',
|
||||
'lib/entryCells.ts',
|
||||
'lib/entryListQuery.ts',
|
||||
'views/ContentListView.vue',
|
||||
'views/ContentListPage.vue',
|
||||
]
|
||||
|
||||
const SAME_IN_FRENCH = new Set(['ID'])
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { messages } from './messages'
|
||||
|
||||
const SOURCES = [
|
||||
'components/ContentState.vue',
|
||||
'components/EntryActions.vue',
|
||||
'components/EntryControl.vue',
|
||||
'components/EntryDateField.vue',
|
||||
'components/EntryDeleteDialog.vue',
|
||||
'components/EntryEditor.vue',
|
||||
'components/EntryEditorSkeleton.vue',
|
||||
'components/EntryForm.vue',
|
||||
'components/EntryNotices.vue',
|
||||
'components/MarkdownField.vue',
|
||||
'composables/useEntryEditorPage.ts',
|
||||
'composables/useEntryRemoval.ts',
|
||||
'lib/entryForm.ts',
|
||||
'lib/entryMessages.ts',
|
||||
'lib/entryEditors.ts',
|
||||
'views/EntryEditorPage.vue',
|
||||
]
|
||||
|
||||
const SAME_IN_FRENCH = new Set<string>([])
|
||||
|
||||
const LITERAL = /\bt\(\s*(?:'((?:\\.|[^'\\])*)'|"((?:\\.|[^"\\])*)")/g
|
||||
|
||||
function keysIn(source: string) {
|
||||
return [...source.matchAll(LITERAL)].map((match) =>
|
||||
(match[1] ?? match[2] ?? '').replace(/\\u0022/g, '"').replace(/\\(['"\\])/g, '$1'),
|
||||
)
|
||||
}
|
||||
|
||||
describe('Entry editor messages', () => {
|
||||
const keys = SOURCES.flatMap((file) =>
|
||||
keysIn(readFileSync(join(import.meta.dirname, '..', file), 'utf8')).map((key) => ({
|
||||
file,
|
||||
key,
|
||||
})),
|
||||
)
|
||||
|
||||
it('finds the strings of the screen', () => {
|
||||
expect(keys.length).toBeGreaterThan(60)
|
||||
})
|
||||
|
||||
it.each(keys.map(({ file, key }) => [file, key]))(
|
||||
'%s: "%s" has its own French entry',
|
||||
(_file, key) => {
|
||||
const french = (messages as Record<string, string>)[key as string]
|
||||
|
||||
expect(french).toMatch(/\S/)
|
||||
if (!SAME_IN_FRENCH.has(key as string)) {
|
||||
expect(french).not.toBe(key)
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
@@ -2530,6 +2530,84 @@ export const messages = {
|
||||
'{0}, sorted by last updated, newest first':
|
||||
'{0}, trié par dernière modification, plus récent en premier',
|
||||
'{0}, sorted by {1}, {2}': '{0}, trié par {1}, {2}',
|
||||
'Entry saved': 'Entrée enregistrée',
|
||||
'\u0022{0}\u0022 is up to date.': '« {0} » est à jour.',
|
||||
'Entry deleted': 'Entrée supprimée',
|
||||
'\u0022{0}\u0022 was deleted from {1}.': '« {0} » a été supprimée de {1}.',
|
||||
'Delete this entry?': 'Supprimer cette entrée ?',
|
||||
"\u0022{0}\u0022 will be deleted from {1}. This can't be undone.":
|
||||
'« {0} » sera supprimée de {1}. Cette action est irréversible.',
|
||||
'Delete entry': 'Supprimer l’entrée',
|
||||
"Couldn't delete the entry": 'Impossible de supprimer l’entrée',
|
||||
'Something went wrong on our side. Nothing was deleted. Try again in a moment.':
|
||||
'Un problème est survenu de notre côté. Rien n’a été supprimé. Réessayez dans un instant.',
|
||||
"Couldn't save the entry": 'Impossible d’enregistrer l’entrée',
|
||||
"Couldn't load the entry": 'Impossible de charger l’entrée',
|
||||
'GET {0} failed. Check your connection and try again.':
|
||||
'GET {0} a échoué. Vérifiez votre connexion et réessayez.',
|
||||
'Not saved': 'Non enregistrée',
|
||||
'Not saved yet': 'Pas encore enregistrée',
|
||||
Entry: 'Entrée',
|
||||
'Entry {0}': 'Entrée {0}',
|
||||
'Entry not found': 'Entrée introuvable',
|
||||
'It may have been deleted by someone else.':
|
||||
'Elle a peut-être été supprimée par quelqu’un d’autre.',
|
||||
'Back to {0}': 'Retour à {0}',
|
||||
'The fields of {0} changed': 'Les champs de {0} ont changé',
|
||||
"A field was removed and a new required field, {0}, was added after you opened this form. Reload the form to continue. What you typed here can't be saved against the old fields.":
|
||||
'Un champ a été supprimé et un nouveau champ obligatoire, {0}, a été ajouté après l’ouverture de ce formulaire. Rechargez le formulaire pour continuer. Ce que vous avez saisi ici ne peut pas être enregistré avec les anciens champs.',
|
||||
'Reload form': 'Recharger le formulaire',
|
||||
'Another entry already uses this {0}. Change it and save again.':
|
||||
'Une autre entrée utilise déjà ce champ ({0}). Modifiez-le et enregistrez à nouveau.',
|
||||
'Another entry already uses this choice. Change it and save again.':
|
||||
'Une autre entrée utilise déjà ce choix. Modifiez-le et enregistrez à nouveau.',
|
||||
'Another entry already uses this date. Change it and save again.':
|
||||
'Une autre entrée utilise déjà cette date. Modifiez-la et enregistrez à nouveau.',
|
||||
'Another entry already uses this {0}. Enter a different one.':
|
||||
'Une autre entrée utilise déjà ce champ ({0}). Saisissez une autre valeur.',
|
||||
'Another entry already uses this choice for {0}.':
|
||||
'Une autre entrée utilise déjà ce choix pour {0}.',
|
||||
'Another entry already uses this date for {0}.':
|
||||
'Une autre entrée utilise déjà cette date pour {0}.',
|
||||
'Each entry needs a different {0}.': 'Chaque entrée doit avoir une valeur différente ({0}).',
|
||||
'{0} · Fields marked * are required.': '{0} · Les champs marqués * sont obligatoires.',
|
||||
'{0} · Entry {1}': '{0} · Entrée {1}',
|
||||
'Fill in {0}.': 'Renseignez {0}.',
|
||||
'Pick a date for {0}.': 'Choisissez une date pour {0}.',
|
||||
'Choose an option for {0}.': 'Choisissez une option pour {0}.',
|
||||
'Choose yes or no for {0}.': 'Choisissez oui ou non pour {0}.',
|
||||
'{0} must be text.': '{0} doit être du texte.',
|
||||
'{0} must be a number.': '{0} doit être un nombre.',
|
||||
'{0} must be yes or no.': '{0} doit être oui ou non.',
|
||||
'{0} must be a date, like 2026-11-21.': '{0} doit être une date, par exemple 2026-11-21.',
|
||||
'{0} must be one of the choices.': '{0} doit être l’un des choix.',
|
||||
'{0} must be at least {1}.': '{0} doit être au moins {1}.',
|
||||
'{0} must be at most {1}.': '{0} doit être au plus {1}.',
|
||||
"{0} can't be before {1}.": '{0} ne peut pas être avant le {1}.',
|
||||
"{0} can't be after {1}.": '{0} ne peut pas être après le {1}.',
|
||||
'{0} needs at least {1} characters.': '{0} doit contenir au moins {1} caractères.',
|
||||
'{0} can be up to {1} characters.': '{0} peut contenir jusqu’à {1} caractères.',
|
||||
'Choose one of: {0}.': 'Choisissez parmi : {0}.',
|
||||
'{0} or {1}': '{0} ou {1}',
|
||||
'From {0} until {1}.': 'Du {0} au {1}.',
|
||||
'From {0}.': 'À partir du {0}.',
|
||||
'Until {0}.': 'Jusqu’au {0}.',
|
||||
'Between {0} and {1}.': 'Entre {0} et {1}.',
|
||||
'At least {0}.': 'Au moins {0}.',
|
||||
'At most {0}.': 'Au plus {0}.',
|
||||
'Markdown. Switch to Preview to check the result.':
|
||||
'Markdown. Passez à l’aperçu pour vérifier le résultat.',
|
||||
'Write in Markdown': 'Écrivez en Markdown',
|
||||
'Nothing to preview yet.': 'Rien à prévisualiser pour l’instant.',
|
||||
Write: 'Écrire',
|
||||
'Pick a date': 'Choisir une date',
|
||||
'Open calendar': 'Ouvrir le calendrier',
|
||||
'Enter a number': 'Saisissez un nombre',
|
||||
'Choose an option': 'Choisissez une option',
|
||||
On: 'Activé',
|
||||
Off: 'Désactivé',
|
||||
Heading: 'Titre',
|
||||
List: 'Liste',
|
||||
id: 'identifiant',
|
||||
} as const
|
||||
|
||||
|
||||
Reference in new issue
Block a user