diff --git a/components/contacts/__tests__/contact-form.test.tsx b/components/contacts/__tests__/contact-form.test.tsx index 75f4375b..6977cd68 100644 --- a/components/contacts/__tests__/contact-form.test.tsx +++ b/components/contacts/__tests__/contact-form.test.tsx @@ -102,4 +102,98 @@ describe('ContactForm', () => { expect.arrayContaining([expect.objectContaining({ kind: 'given', value: 'Jane' })]) ); }); + + it('saves an organization-only card when the organization type is selected', async () => { + const onSave = vi.fn().mockResolvedValue(undefined); + render(); + + fireEvent.click(screen.getByText('type_organization')); + fireEvent.change(screen.getByPlaceholderText('organization_placeholder'), { target: { value: 'Acme Corp' } }); + fireEvent.submit(screen.getByText('save').closest('form')!); + + await waitFor(() => { + expect(onSave).toHaveBeenCalledOnce(); + }); + + const savedData = onSave.mock.calls[0][0]; + expect(savedData.kind).toBe('org'); + expect(savedData.organizations.o0.name).toBe('Acme Corp'); + // No personal name components; the org name carries the display name instead. + expect(savedData.name.components).toBeUndefined(); + expect(savedData.name.full).toBe('Acme Corp'); + }); + + it('hides the personal name fields in organization mode', () => { + render(); + expect(screen.getByPlaceholderText('given_name')).toBeInTheDocument(); + + fireEvent.click(screen.getByText('type_organization')); + + expect(screen.queryByPlaceholderText('given_name')).not.toBeInTheDocument(); + expect(screen.queryByPlaceholderText('surname')).not.toBeInTheDocument(); + // The organization field moves into the identity section, so it appears once. + expect(screen.getAllByPlaceholderText('organization_placeholder')).toHaveLength(1); + }); + + it('still requires a name in organization mode', async () => { + const onSave = vi.fn(); + render(); + + fireEvent.click(screen.getByText('type_organization')); + fireEvent.submit(screen.getByText('save').closest('form')!); + + await waitFor(() => { + expect(screen.getByText('name_required')).toBeInTheDocument(); + }); + expect(onSave).not.toHaveBeenCalled(); + }); + + it('accepts an organization instead of a personal name in person mode', async () => { + const onSave = vi.fn().mockResolvedValue(undefined); + render(); + + fireEvent.click(screen.getByText('section_work')); + fireEvent.change(screen.getByPlaceholderText('organization_placeholder'), { target: { value: 'Acme Corp' } }); + fireEvent.submit(screen.getByText('save').closest('form')!); + + await waitFor(() => { + expect(onSave).toHaveBeenCalledOnce(); + }); + expect(onSave.mock.calls[0][0].name.full).toBe('Acme Corp'); + }); + + it('opens an existing org card in organization mode', () => { + const orgContact: ContactCard = { + id: '2', + addressBookIds: {}, + kind: 'org', + name: { full: 'Acme Corp' }, + organizations: { o0: { name: 'Acme Corp' } }, + }; + render(); + + expect(screen.queryByPlaceholderText('given_name')).not.toBeInTheDocument(); + expect(screen.getByDisplayValue('Acme Corp')).toBeInTheDocument(); + }); + + it('switches an org card back to a person', async () => { + const onSave = vi.fn().mockResolvedValue(undefined); + const orgContact: ContactCard = { + id: '2', + addressBookIds: {}, + kind: 'org', + name: { full: 'Acme Corp' }, + organizations: { o0: { name: 'Acme Corp' } }, + }; + render(); + + fireEvent.click(screen.getByText('type_person')); + fireEvent.change(screen.getByPlaceholderText('given_name'), { target: { value: 'Jane' } }); + fireEvent.submit(screen.getByText('save').closest('form')!); + + await waitFor(() => { + expect(onSave).toHaveBeenCalledOnce(); + }); + expect(onSave.mock.calls[0][0].kind).toBe('individual'); + }); }); diff --git a/components/contacts/contact-detail.tsx b/components/contacts/contact-detail.tsx index c4160b7e..1b07d86e 100644 --- a/components/contacts/contact-detail.tsx +++ b/components/contacts/contact-detail.tsx @@ -171,7 +171,9 @@ export function ContactDetail({ contact, onEdit, onDelete, onAddToGroup, onDupli const hasNickname = nicknames.length > 0; const titleLine = jobTitles.length > 0 ? jobTitles.map(t => t.name).join(", ") : undefined; - const subtitleParts = [titleLine, orgs[0]?.name].filter(Boolean) as string[]; + // On an organization card the org name is already the heading; don't repeat it. + const orgName = orgs[0]?.name; + const subtitleParts = [titleLine, orgName === name ? undefined : orgName].filter(Boolean) as string[]; const hasContactDetails = emails.length > 0 || phones.length > 0 || addresses.length > 0 || onlineServices.length > 0; const hasWork = titles.length > 0 || orgs.length > 0; const hasGender = !!(contact.speakToAs && (contact.speakToAs.grammaticalGender || contact.speakToAs.pronouns)); diff --git a/components/contacts/contact-form.tsx b/components/contacts/contact-form.tsx index a2e898eb..566d5301 100644 --- a/components/contacts/contact-form.tsx +++ b/components/contacts/contact-form.tsx @@ -266,6 +266,16 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress contact?.organizations ? (Object.values(contact.organizations)[0]?.units?.[0]?.name || "") : "" ); + // A card may describe an organization instead of a person (RFC 9553 kind "org"). + // Older cards predate the explicit kind, so fall back to "has an org name but no + // personal name". + const [isOrg, setIsOrg] = useState(() => { + if (!contact) return false; + if (contact.kind) return contact.kind === "org"; + const hasPersonName = !!(findComponent("given") || findComponent("surname")); + return !hasPersonName && !!Object.values(contact.organizations || {})[0]?.name; + }); + const [jobTitle, setJobTitle] = useState(() => { if (contact?.titles) { const t = Object.values(contact.titles).find(t => t.kind !== "role"); @@ -424,7 +434,10 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress e.preventDefault(); setError(null); - if (!givenName.trim() && !surname.trim()) { + // An organization name identifies the card just as well as a personal name. + const orgName = organization.trim(); + const hasPersonName = !!(givenName.trim() || surname.trim()); + if (isOrg ? !orgName : (!hasPersonName && !orgName)) { setError(t("name_required")); return; } @@ -461,11 +474,19 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress // Emit JSContact-standard kinds (RFC 9553) so the JMAP server stores them losslessly. const nameComponents = []; - if (prefix.trim()) nameComponents.push({ kind: "title" as const, value: prefix.trim() }); - if (givenName.trim()) nameComponents.push({ kind: "given" as const, value: givenName.trim() }); - if (additionalName.trim()) nameComponents.push({ kind: "given2" as const, value: additionalName.trim() }); - if (surname.trim()) nameComponents.push({ kind: "surname" as const, value: surname.trim() }); - if (suffix.trim()) nameComponents.push({ kind: "generation" as const, value: suffix.trim() }); + if (!isOrg) { + if (prefix.trim()) nameComponents.push({ kind: "title" as const, value: prefix.trim() }); + if (givenName.trim()) nameComponents.push({ kind: "given" as const, value: givenName.trim() }); + if (additionalName.trim()) nameComponents.push({ kind: "given2" as const, value: additionalName.trim() }); + if (surname.trim()) nameComponents.push({ kind: "surname" as const, value: surname.trim() }); + if (suffix.trim()) nameComponents.push({ kind: "generation" as const, value: suffix.trim() }); + } + + // Without personal name components, carry the organization name in `name.full` + // so servers and other clients have something to display. + const nameValue: ContactCard["name"] = nameComponents.length > 0 + ? { components: nameComponents, isOrdered: true } + : { full: orgName }; const titlesMap: Record = {}; if (jobTitle.trim()) titlesMap["t0"] = { name: jobTitle.trim(), kind: "title" }; @@ -530,14 +551,20 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress const mediaValue: Record | null | undefined = Object.keys(mediaMap).length > 0 ? mediaMap : (hadMedia ? null : undefined); + // Only send `kind` when this form owns the answer: switching a card between + // person and organization. Leave other kinds (group, location, ...) untouched. + const kindValue: ContactCard["kind"] | undefined = + isOrg ? "org" : (contact?.kind === "org" ? "individual" : undefined); + const data: Partial = { - name: { components: nameComponents, isOrdered: true }, + name: nameValue, + ...(kindValue ? { kind: kindValue } : {}), nicknames: nickname.trim() ? { n0: { name: nickname.trim() } } : undefined, emails: Object.keys(emailsMap).length > 0 ? emailsMap : undefined, phones: Object.keys(phonesMap).length > 0 ? phonesMap : undefined, titles: Object.keys(titlesMap).length > 0 ? titlesMap : undefined, - organizations: organization.trim() - ? { o0: { name: organization.trim(), units: orgUnits } } + organizations: orgName + ? { o0: { name: orgName, units: orgUnits } } : undefined, addresses: Object.keys(addressesMap).length > 0 ? addressesMap : undefined, onlineServices: Object.keys(onlineServicesMap).length > 0 ? onlineServicesMap : undefined, @@ -570,7 +597,7 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress } }; - const previewName = [givenName, surname].filter(Boolean).join(" ").trim(); + const previewName = (isOrg ? "" : [givenName, surname].filter(Boolean).join(" ").trim()) || organization.trim(); const previewEmail = emails.find(e => e.address.trim())?.address.trim() || ""; return ( @@ -661,38 +688,81 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress )} -
-
- - setPrefix(e.target.value)} placeholder={t("prefix_placeholder")} className="w-20" /> -
-
- - setGivenName(e.target.value)} placeholder={t("given_name")} autoFocus /> -
-
- - setSurname(e.target.value)} placeholder={t("surname")} /> -
-
- - setSuffix(e.target.value)} placeholder={t("suffix_placeholder")} className="w-20" /> +
+ {t("contact_type")} +
+ {[ + { org: false, label: t("type_person"), icon: User }, + { org: true, label: t("type_organization"), icon: Building }, + ].map(({ org, label, icon: Icon }) => ( + + ))}
-
-
- - setAdditionalName(e.target.value)} placeholder={t("middle_name")} /> + {isOrg ? ( +
+
+ + setOrganization(e.target.value)} placeholder={t("organization_placeholder")} autoFocus /> +
+
+ + setNickname(e.target.value)} placeholder={t("nickname_placeholder")} /> +
-
- - setNickname(e.target.value)} placeholder={t("nickname_placeholder")} /> -
-
+ ) : ( + <> +
+
+ + setPrefix(e.target.value)} placeholder={t("prefix_placeholder")} className="w-20" /> +
+
+ + setGivenName(e.target.value)} placeholder={t("given_name")} autoFocus /> +
+
+ + setSurname(e.target.value)} placeholder={t("surname")} /> +
+
+ + setSuffix(e.target.value)} placeholder={t("suffix_placeholder")} className="w-20" /> +
+
+
+
+ + setAdditionalName(e.target.value)} placeholder={t("middle_name")} /> +
+
+ + setNickname(e.target.value)} placeholder={t("nickname_placeholder")} /> +
+
+ + )} {/* Email */} @@ -806,12 +876,15 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress {/* Work & Organization */} - +
-
- - setOrganization(e.target.value)} placeholder={t("organization_placeholder")} /> -
+ {/* In organization mode the org name is the card's identity, edited above. */} + {!isOrg && ( +
+ + setOrganization(e.target.value)} placeholder={t("organization_placeholder")} /> +
+ )}
setDepartment(e.target.value)} placeholder={t("department_placeholder")} /> diff --git a/lib/__tests__/vcard.test.ts b/lib/__tests__/vcard.test.ts index bc502023..a42cf63c 100644 --- a/lib/__tests__/vcard.test.ts +++ b/lib/__tests__/vcard.test.ts @@ -445,6 +445,48 @@ describe("generateVCard", () => { const vcf = generateVCard([contact]); expect(vcf).toContain("NOTE:Has comma\\, semicolon\\; and newline\\nhere"); }); + + it("uses the organization name as FN for organization cards (issue #701)", () => { + const contact: ContactCard = { + id: "c4", + addressBookIds: {}, + kind: "org", + name: { full: "Acme Corp" }, + organizations: { o0: { name: "Acme Corp" } }, + }; + + const vcf = generateVCard([contact]); + expect(vcf).toContain("KIND:org"); + expect(vcf).toContain("FN:Acme Corp"); + expect(vcf).toContain("ORG:Acme Corp"); + }); + + it("falls back to ORG for FN when the card has no name at all", () => { + const contact: ContactCard = { + id: "c5", + addressBookIds: {}, + kind: "org", + organizations: { o0: { name: "Acme Corp" } }, + }; + + expect(generateVCard([contact])).toContain("FN:Acme Corp"); + }); +}); + +describe("organization-only cards (issue #701)", () => { + it("keeps a vCard that has only an organization name", () => { + const parsed = parseVCard([ + "BEGIN:VCARD", + "VERSION:4.0", + "KIND:org", + "ORG:Acme Corp", + "END:VCARD", + ].join("\r\n")); + + expect(parsed).toHaveLength(1); + expect(parsed[0].kind).toBe("org"); + expect(parsed[0].organizations?.o0.name).toBe("Acme Corp"); + }); }); describe("round-trip: parse → generate → parse", () => { diff --git a/lib/vcard.ts b/lib/vcard.ts index efd592d4..96d5701a 100644 --- a/lib/vcard.ts +++ b/lib/vcard.ts @@ -844,7 +844,9 @@ function buildContact(raw: Record): ContactCard | null { const hasName = card.name && (card.name.components?.length ?? 0) > 0 || !!card.name?.full; const hasEmail = card.emails && Object.keys(card.emails).length > 0; - if (!hasName && !hasEmail && card.kind !== "group") return null; + // An organization name identifies the card just as well as a personal name. + const hasOrg = !!Object.values(card.organizations || {})[0]?.name; + if (!hasName && !hasEmail && !hasOrg && card.kind !== "group") return null; return card; } @@ -882,7 +884,11 @@ function generateSingleVCard(contact: ContactCard): string { const suffix = findKind("generation", "suffix"); const additional = findKind("given2", "additional", "middle"); - const fn = [prefix, given, additional, surname, suffix].filter(Boolean).join(" ") || contact.name?.full || ""; + // FN is mandatory in vCard, so fall back to the organization name for org cards. + const fn = [prefix, given, additional, surname, suffix].filter(Boolean).join(" ") + || contact.name?.full + || Object.values(contact.organizations || {})[0]?.name + || ""; if (fn) { lines.push(`FN:${encodeValue(fn)}`); lines.push(`N:${encodeValue(surname)};${encodeValue(given)};${encodeValue(additional)};${encodeValue(prefix)};${encodeValue(suffix)}`); diff --git a/locales/ar/common.json b/locales/ar/common.json index 3a870a64..67c7a25f 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -2376,6 +2376,9 @@ "section_address_book": "الدليل", "select_address_book": "اختر دليلًا...", "section_identity": "الاسم والهوية", + "contact_type": "نوع جهة الاتصال", + "type_person": "شخص", + "type_organization": "المؤسسة", "section_work": "العمل والمؤسسة", "prefix": "اللقب", "prefix_placeholder": "د.، أ.، السيدة", @@ -2460,7 +2463,7 @@ "cancel": "إلغاء", "creating": "جارٍ الإنشاء...", "updating": "جارٍ التحديث...", - "name_required": "يلزم إدخال الاسم الأول أو اسم العائلة على الأقل", + "name_required": "أدخل اسمًا أول أو اسم عائلة أو مؤسسة", "email_invalid": "يرجى إدخال عنوان بريد إلكتروني صالح", "email_error_inline": "تنسيق البريد الإلكتروني غير صالح", "save_failed": "فشل حفظ جهة الاتصال", diff --git a/locales/ca/common.json b/locales/ca/common.json index 533247b5..a2da0f2f 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -2376,6 +2376,9 @@ "section_address_book": "Directori", "select_address_book": "Seleccioneu un directori...", "section_identity": "Nom i identitat", + "contact_type": "Tipus de contacte", + "type_person": "Persona", + "type_organization": "Organització", "section_work": "Feina i organització", "prefix": "Prefix", "prefix_placeholder": "Dr., Sr., Sra.", @@ -2460,7 +2463,7 @@ "cancel": "Cancel·la", "creating": "Creant...", "updating": "Actualitzant...", - "name_required": "Cal com a mínim un nom o un cognom", + "name_required": "Introduïu un nom, un cognom o una organització", "email_invalid": "Introduïu una adreça electrònica vàlida", "email_error_inline": "Format de correu electrònic no vàlid", "save_failed": "No s'ha pogut desar el contacte", diff --git a/locales/cs/common.json b/locales/cs/common.json index 7249e8f5..3025a07d 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Adresář", "select_address_book": "Vyberte adresář...", "section_identity": "Jméno a identita", + "contact_type": "Typ kontaktu", + "type_person": "Osoba", + "type_organization": "Organizace", "section_work": "Práce a organizace", "prefix": "Titul", "prefix_placeholder": "Dr., Pan, Paní", @@ -2459,7 +2462,7 @@ "cancel": "Zrušit", "creating": "Vytváření...", "updating": "Aktualizování...", - "name_required": "Je vyžadováno alespoň jméno nebo příjmení", + "name_required": "Zadejte jméno, příjmení nebo organizaci", "email_invalid": "Zadejte platnou e-mailovou adresu", "email_error_inline": "Neplatný formát e-mailové adresy", "save_failed": "Uložení kontaktu selhalo", diff --git a/locales/da/common.json b/locales/da/common.json index d2f8c102..8503e48b 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Adressebog", "select_address_book": "Vælg en adressebog...", "section_identity": "Navn & identitet", + "contact_type": "Kontakttype", + "type_person": "Person", + "type_organization": "Organisation", "section_work": "Arbejde & organisation", "prefix": "Præfiks", "prefix_placeholder": "Dr., hr., fru", @@ -2459,7 +2462,7 @@ "cancel": "Annuller", "creating": "Opretter...", "updating": "Opdaterer...", - "name_required": "Mindst et fornavn eller efternavn er påkrævet", + "name_required": "Angiv et fornavn, efternavn eller en organisation", "email_invalid": "Indtast en gyldig e-mailadresse", "email_error_inline": "Ugyldigt e-mailformat", "save_failed": "Kunne ikke gemme kontakt", diff --git a/locales/de/common.json b/locales/de/common.json index c1311520..bfa1814d 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Verzeichnis", "select_address_book": "Verzeichnis auswählen...", "section_identity": "Name & Identität", + "contact_type": "Kontakttyp", + "type_person": "Person", + "type_organization": "Organisation", "section_work": "Beruf & Organisation", "prefix": "Anrede", "prefix_placeholder": "Dr., Herr, Frau", @@ -2459,7 +2462,7 @@ "cancel": "Abbrechen", "creating": "Wird erstellt...", "updating": "Wird aktualisiert...", - "name_required": "Mindestens ein Vor- oder Nachname ist erforderlich", + "name_required": "Bitte Vorname, Nachname oder Organisation angeben", "email_invalid": "Bitte geben Sie eine gültige E-Mail-Adresse ein", "email_error_inline": "Ungültiges E-Mail-Format", "save_failed": "Kontakt konnte nicht gespeichert werden", diff --git a/locales/en/common.json b/locales/en/common.json index 96e19eb7..c05a71cb 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -2376,6 +2376,9 @@ "section_address_book": "Directory", "select_address_book": "Select a directory...", "section_identity": "Name & Identity", + "contact_type": "Contact type", + "type_person": "Person", + "type_organization": "Organization", "section_work": "Work & Organization", "prefix": "Prefix", "prefix_placeholder": "Dr., Mr., Mrs.", @@ -2460,7 +2463,7 @@ "cancel": "Cancel", "creating": "Creating...", "updating": "Updating...", - "name_required": "At least a first name or last name is required", + "name_required": "Enter a first name, last name, or organization", "email_invalid": "Please enter a valid email address", "email_error_inline": "Invalid email format", "save_failed": "Failed to save contact", diff --git a/locales/es/common.json b/locales/es/common.json index ffeb9e38..25a53a64 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Directorio", "select_address_book": "Seleccionar un directorio...", "section_identity": "Nombre e identidad", + "contact_type": "Tipo de contacto", + "type_person": "Persona", + "type_organization": "Organización", "section_work": "Trabajo y organización", "prefix": "Prefijo", "prefix_placeholder": "Dr., Sr., Sra.", @@ -2459,7 +2462,7 @@ "cancel": "Cancelar", "creating": "Creando...", "updating": "Actualizando...", - "name_required": "Se requiere al menos un nombre o apellido", + "name_required": "Introduce un nombre, un apellido o una organización", "email_invalid": "Introduce una dirección de correo válida", "email_error_inline": "Formato de correo inválido", "save_failed": "Error al guardar el contacto", diff --git a/locales/fa/common.json b/locales/fa/common.json index 466d8394..c8f472bf 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -2376,6 +2376,9 @@ "section_address_book": "دفترچه", "select_address_book": "انتخاب دفترچه...", "section_identity": "نام و هویت", + "contact_type": "نوع مخاطب", + "type_person": "شخص", + "type_organization": "سازمان", "section_work": "کار و سازمان", "prefix": "پیشوند", "prefix_placeholder": "دکتر، مهندس", @@ -2460,7 +2463,7 @@ "cancel": "انصراف", "creating": "در حال ایجاد...", "updating": "در حال به‌روزرسانی...", - "name_required": "حداقل نام یا نام خانوادگی الزامی است", + "name_required": "نام، نام خانوادگی یا سازمان را وارد کنید", "email_invalid": "لطفاً یک آدرس ایمیل معتبر وارد کنید", "email_error_inline": "فرمت ایمیل نامعتبر است", "save_failed": "ذخیره مخاطب ناموفق بود", diff --git a/locales/fr/common.json b/locales/fr/common.json index 06891007..e174bbc9 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Répertoire", "select_address_book": "Sélectionner un répertoire...", "section_identity": "Nom et identité", + "contact_type": "Type de contact", + "type_person": "Personne", + "type_organization": "Organisation", "section_work": "Travail et organisation", "prefix": "Préfixe", "prefix_placeholder": "Dr., M., Mme", @@ -2459,7 +2462,7 @@ "cancel": "Annuler", "creating": "Création...", "updating": "Mise à jour...", - "name_required": "Un prénom ou un nom est requis", + "name_required": "Saisissez un prénom, un nom ou une organisation", "email_invalid": "Veuillez saisir une adresse e-mail valide", "email_error_inline": "Format d'e-mail invalide", "save_failed": "Échec de l'enregistrement du contact", diff --git a/locales/he/common.json b/locales/he/common.json index a758742d..b250cd92 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -2289,6 +2289,9 @@ "section_address_book": "ספרייה", "select_address_book": "בחר ספרייה...", "section_identity": "שם וזהות", + "contact_type": "סוג איש קשר", + "type_person": "אדם", + "type_organization": "ארגון", "section_work": "עבודה וארגון", "prefix": "קידומת", "prefix_placeholder": "ד\"ר, מר, גברת.", @@ -2373,7 +2376,7 @@ "cancel": "לְבַטֵל", "creating": "יוצר...", "updating": "מעדכן...", - "name_required": "נדרש לפחות שם פרטי או שם משפחה", + "name_required": "יש להזין שם פרטי, שם משפחה או ארגון", "email_invalid": "נא להזין כתובת אימייל חוקית", "email_error_inline": "פורמט אימייל לא חוקי", "save_failed": "שמירת איש הקשר נכשלה", diff --git a/locales/hu/common.json b/locales/hu/common.json index ed8bec08..f1320a7b 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -2376,6 +2376,9 @@ "section_address_book": "Címtár", "select_address_book": "Címtár kiválasztása...", "section_identity": "Név és azonosság", + "contact_type": "Névjegy típusa", + "type_person": "Személy", + "type_organization": "Szervezet", "section_work": "Munka és szervezet", "prefix": "Előtag", "prefix_placeholder": "Dr., Úr., Mrs.", @@ -2460,7 +2463,7 @@ "cancel": "Mégse", "creating": "Létrehozás...", "updating": "Frissítés...", - "name_required": "Legalább a keresztnév vagy vezetéknév megadása kötelező", + "name_required": "Adjon meg egy keresztnevet, vezetéknevet vagy szervezetet", "email_invalid": "Kérjük, adj meg egy érvényes e-mail címet", "email_error_inline": "Érvénytelen e-mail formátum", "save_failed": "Nem sikerült menteni a névjegyet", diff --git a/locales/it/common.json b/locales/it/common.json index ee386f71..d469e1f8 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Rubrica", "select_address_book": "Seleziona una rubrica...", "section_identity": "Nome e identità", + "contact_type": "Tipo di contatto", + "type_person": "Persona", + "type_organization": "Organizzazione", "section_work": "Lavoro e organizzazione", "prefix": "Prefisso", "prefix_placeholder": "Dott., Sig., Sig.ra", @@ -2459,7 +2462,7 @@ "cancel": "Annulla", "creating": "Creazione...", "updating": "Aggiornamento...", - "name_required": "È richiesto almeno un nome o cognome", + "name_required": "Inserisci un nome, un cognome o un'organizzazione", "email_invalid": "Inserisci un indirizzo email valido", "email_error_inline": "Formato email non valido", "save_failed": "Impossibile salvare il contatto", diff --git a/locales/ja/common.json b/locales/ja/common.json index 0501ef06..36054845 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "ディレクトリ", "select_address_book": "ディレクトリを選択...", "section_identity": "名前と識別情報", + "contact_type": "連絡先の種類", + "type_person": "個人", + "type_organization": "組織", "section_work": "職業と組織", "prefix": "敬称", "prefix_placeholder": "博士、氏", @@ -2459,7 +2462,7 @@ "cancel": "キャンセル", "creating": "作成中...", "updating": "更新中...", - "name_required": "名前は必須です", + "name_required": "名、姓、または組織を入力してください", "email_invalid": "有効なメールアドレスを入力してください", "email_error_inline": "メールアドレスの形式が正しくありません", "save_failed": "連絡先の保存に失敗しました", diff --git a/locales/ko/common.json b/locales/ko/common.json index c6d9420a..85c57ab6 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "디렉터리", "select_address_book": "디렉터리 선택...", "section_identity": "이름 및 신원", + "contact_type": "연락처 유형", + "type_person": "개인", + "type_organization": "소속(회사)", "section_work": "직장 및 소속", "prefix": "호칭", "prefix_placeholder": "예: Dr., Mr., Mrs.", @@ -2459,7 +2462,7 @@ "cancel": "취소", "creating": "만드는 중...", "updating": "업데이트 중...", - "name_required": "이름이나 성 중에 하나는 꼭 필요해요", + "name_required": "이름, 성 또는 조직을 입력하세요", "email_invalid": "올바른 이메일 주소를 입력해 주세요", "email_error_inline": "이메일 형식이 잘못되었어요", "save_failed": "연락처를 저장하지 못했어요", diff --git a/locales/lv/common.json b/locales/lv/common.json index 746dbc88..ab3f02b3 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -2371,6 +2371,9 @@ "section_address_book": "Katalogs", "select_address_book": "Izvēlieties katalogu...", "section_identity": "Vārds un identitāte", + "contact_type": "Kontakta veids", + "type_person": "Persona", + "type_organization": "Organizācija", "section_work": "Darbs un organizācija", "prefix": "Prefikss", "prefix_placeholder": "Dr., kungs, kundze", @@ -2455,7 +2458,7 @@ "cancel": "Atcelt", "creating": "Izveido...", "updating": "Atjaunina...", - "name_required": "Nepieciešams vismaz vārds vai uzvārds", + "name_required": "Ievadiet vārdu, uzvārdu vai organizāciju", "email_invalid": "Ievadiet derīgu e-pasta adresi", "email_error_inline": "Nederīgs e-pasta formāts", "save_failed": "Neizdevās saglabāt kontaktu", diff --git a/locales/nl/common.json b/locales/nl/common.json index 8048229b..6d6e1587 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Adresboek", "select_address_book": "Selecteer een adresboek...", "section_identity": "Naam en identiteit", + "contact_type": "Contacttype", + "type_person": "Persoon", + "type_organization": "Organisatie", "section_work": "Werk en organisatie", "prefix": "Voorvoegsel", "prefix_placeholder": "Dr., Dhr., Mevr.", @@ -2459,7 +2462,7 @@ "cancel": "Annuleren", "creating": "Aanmaken...", "updating": "Bijwerken...", - "name_required": "Ten minste een voor- of achternaam is vereist", + "name_required": "Voer een voornaam, achternaam of organisatie in", "email_invalid": "Voer een geldig e-mailadres in", "email_error_inline": "Ongeldig e-mailformaat", "save_failed": "Kon contact niet opslaan", diff --git a/locales/pl/common.json b/locales/pl/common.json index 9b0cf6ed..7d66f712 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Katalog", "select_address_book": "Wybierz katalog...", "section_identity": "Imię i tożsamość", + "contact_type": "Typ kontaktu", + "type_person": "Osoba", + "type_organization": "Organizacja", "section_work": "Praca i organizacja", "prefix": "Tytuł", "prefix_placeholder": "Dr, Pan, Pani", @@ -2459,7 +2462,7 @@ "cancel": "Anuluj", "creating": "Tworzenie...", "updating": "Aktualizowanie...", - "name_required": "Wymagane jest przynajmniej imię lub nazwisko", + "name_required": "Podaj imię, nazwisko lub organizację", "email_invalid": "Wprowadź prawidłowy adres e-mail", "email_error_inline": "Nieprawidłowy format adresu e-mail", "save_failed": "Nie udało się zapisać kontaktu", diff --git a/locales/pt/common.json b/locales/pt/common.json index 1d226b70..85512608 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Diretório", "select_address_book": "Selecionar um diretório...", "section_identity": "Nome e identidade", + "contact_type": "Tipo de contacto", + "type_person": "Pessoa", + "type_organization": "Organização", "section_work": "Trabalho e organização", "prefix": "Prefixo", "prefix_placeholder": "Dr., Sr., Sra.", @@ -2459,7 +2462,7 @@ "cancel": "Cancelar", "creating": "Criando...", "updating": "Atualizando...", - "name_required": "É necessário pelo menos um nome ou sobrenome", + "name_required": "Introduza um nome próprio, apelido ou organização", "email_invalid": "Por favor, insira um endereço de e-mail válido", "email_error_inline": "Formato de e-mail inválido", "save_failed": "Falha ao salvar contato", diff --git a/locales/ro/common.json b/locales/ro/common.json index 7a3ceb82..30ff81d0 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -2376,6 +2376,9 @@ "section_address_book": "Director", "select_address_book": "Selectați un director...", "section_identity": "Nume și identitate", + "contact_type": "Tip de contact", + "type_person": "Persoană", + "type_organization": "Organizare", "section_work": "Muncă și organizare", "prefix": "Prefix", "prefix_placeholder": "Dr., Dl., Dna.", @@ -2460,7 +2463,7 @@ "cancel": "Anulează", "creating": "Se creează...", "updating": "Se actualizează...", - "name_required": "Este necesar cel puțin un prenume sau un nume de familie", + "name_required": "Introduceți un prenume, un nume sau o organizație", "email_invalid": "Vă rugăm să introduceți o adresă de e-mail validă", "email_error_inline": "Format de e-mail nevalid", "save_failed": "Nu s-a putut salva contactul", diff --git a/locales/ru/common.json b/locales/ru/common.json index 4dfe531f..15b160f1 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Каталог", "select_address_book": "Выберите каталог...", "section_identity": "Имя и личность", + "contact_type": "Тип контакта", + "type_person": "Человек", + "type_organization": "Организация", "section_work": "Работа и организация", "prefix": "Префикс", "prefix_placeholder": "Д-р., Г-н., Г-жа.", @@ -2459,7 +2462,7 @@ "cancel": "Отмена", "creating": "Создание...", "updating": "Обновление...", - "name_required": "Требуется хотя бы имя или фамилия", + "name_required": "Укажите имя, фамилию или организацию", "email_invalid": "Введите корректный адрес электронной почты", "email_error_inline": "Неверный формат email", "save_failed": "Не удалось сохранить контакт", diff --git a/locales/sk/common.json b/locales/sk/common.json index 288868d1..65ebcefe 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -2376,6 +2376,9 @@ "section_address_book": "Adresár", "select_address_book": "Vyberte adresár...", "section_identity": "Meno a identita", + "contact_type": "Typ kontaktu", + "type_person": "Osoba", + "type_organization": "Organizácia", "section_work": "Práca a organizácia", "prefix": "Titul", "prefix_placeholder": "Dr., Pán, Pani", @@ -2460,7 +2463,7 @@ "cancel": "Zrušiť", "creating": "Vytváranie...", "updating": "Aktualizovanie...", - "name_required": "Je potrebné aspoň meno alebo priezvisko", + "name_required": "Zadajte meno, priezvisko alebo organizáciu", "email_invalid": "Zadajte platnú e-mailovú adresu", "email_error_inline": "Neplatný formát e-mailovej adresy", "save_failed": "Uloženie kontaktu zlyhalo", diff --git a/locales/tr/common.json b/locales/tr/common.json index 849e7609..9b0b1785 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Dizin", "select_address_book": "Bir dizin seçin...", "section_identity": "Ad ve Kimlik", + "contact_type": "Kişi türü", + "type_person": "Kişi", + "type_organization": "Kuruluş", "section_work": "İş ve Kuruluş", "prefix": "Ön Ek", "prefix_placeholder": "Dr., Bay, Bayan", @@ -2459,7 +2462,7 @@ "cancel": "İptal", "creating": "Oluşturuluyor...", "updating": "Güncelleniyor...", - "name_required": "En az bir ad veya soyadı gereklidir", + "name_required": "Bir ad, soyad veya kuruluş girin", "email_invalid": "Lütfen geçerli bir e-posta adresi girin", "email_error_inline": "Geçersiz e-posta biçimi", "save_failed": "Kişi kaydedilemedi", diff --git a/locales/uk/common.json b/locales/uk/common.json index 54e41245..bed8f2ed 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "Довідник", "select_address_book": "Виберіть каталог...", "section_identity": "Ім'я та ідентифікація", + "contact_type": "Тип контакту", + "type_person": "Людина", + "type_organization": "організація", "section_work": "Робота та організація", "prefix": "Префікс", "prefix_placeholder": "доктор, пан, місіс", @@ -2459,7 +2462,7 @@ "cancel": "Скасувати", "creating": "Створення...", "updating": "Оновлення...", - "name_required": "Потрібне принаймні ім’я або прізвище", + "name_required": "Вкажіть ім'я, прізвище або організацію", "email_invalid": "Введіть дійсну електронну адресу", "email_error_inline": "Недійсний формат електронної пошти", "save_failed": "Не вдалося зберегти контакт", diff --git a/locales/zh/common.json b/locales/zh/common.json index 014116f7..54475a38 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -2375,6 +2375,9 @@ "section_address_book": "地址簿", "select_address_book": "选择地址簿...", "section_identity": "姓名和身份", + "contact_type": "联系人类型", + "type_person": "个人", + "type_organization": "组织", "section_work": "工作与组织", "prefix": "前缀", "prefix_placeholder": "博士、先生、女士", @@ -2459,7 +2462,7 @@ "cancel": "取消", "creating": "创建中...", "updating": "更新中...", - "name_required": "至少需要名字或姓氏", + "name_required": "请输入名字、姓氏或组织", "email_invalid": "请输入有效的邮箱地址", "email_error_inline": "邮箱地址格式无效", "save_failed": "保存联系人失败",