feat: allow contact cards for organizations #701

This commit is contained in:
Linus Rath
2026-07-30 19:44:36 +02:00
parent 348e032dce
commit e659fe3d38
29 changed files with 360 additions and 71 deletions
@@ -102,4 +102,98 @@ describe('ContactForm', () => {
expect.arrayContaining([expect.objectContaining({ kind: 'given', value: 'Jane' })]) 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(<ContactForm onSave={onSave} onCancel={vi.fn()} />);
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(<ContactForm onSave={vi.fn()} onCancel={vi.fn()} />);
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(<ContactForm onSave={onSave} onCancel={vi.fn()} />);
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(<ContactForm onSave={onSave} onCancel={vi.fn()} />);
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(<ContactForm contact={orgContact} onSave={vi.fn()} onCancel={vi.fn()} />);
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(<ContactForm contact={orgContact} onSave={onSave} onCancel={vi.fn()} />);
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');
});
}); });
+3 -1
View File
@@ -171,7 +171,9 @@ export function ContactDetail({ contact, onEdit, onDelete, onAddToGroup, onDupli
const hasNickname = nicknames.length > 0; const hasNickname = nicknames.length > 0;
const titleLine = jobTitles.length > 0 ? jobTitles.map(t => t.name).join(", ") : undefined; 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 hasContactDetails = emails.length > 0 || phones.length > 0 || addresses.length > 0 || onlineServices.length > 0;
const hasWork = titles.length > 0 || orgs.length > 0; const hasWork = titles.length > 0 || orgs.length > 0;
const hasGender = !!(contact.speakToAs && (contact.speakToAs.grammaticalGender || contact.speakToAs.pronouns)); const hasGender = !!(contact.speakToAs && (contact.speakToAs.grammaticalGender || contact.speakToAs.pronouns));
+79 -6
View File
@@ -266,6 +266,16 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress
contact?.organizations ? (Object.values(contact.organizations)[0]?.units?.[0]?.name || "") : "" 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(() => { const [jobTitle, setJobTitle] = useState(() => {
if (contact?.titles) { if (contact?.titles) {
const t = Object.values(contact.titles).find(t => t.kind !== "role"); const t = Object.values(contact.titles).find(t => t.kind !== "role");
@@ -424,7 +434,10 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress
e.preventDefault(); e.preventDefault();
setError(null); 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")); setError(t("name_required"));
return; 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. // Emit JSContact-standard kinds (RFC 9553) so the JMAP server stores them losslessly.
const nameComponents = []; const nameComponents = [];
if (!isOrg) {
if (prefix.trim()) nameComponents.push({ kind: "title" as const, value: prefix.trim() }); 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 (givenName.trim()) nameComponents.push({ kind: "given" as const, value: givenName.trim() });
if (additionalName.trim()) nameComponents.push({ kind: "given2" as const, value: additionalName.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 (surname.trim()) nameComponents.push({ kind: "surname" as const, value: surname.trim() });
if (suffix.trim()) nameComponents.push({ kind: "generation" as const, value: suffix.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<string, { name: string; kind?: "title" | "role" }> = {}; const titlesMap: Record<string, { name: string; kind?: "title" | "role" }> = {};
if (jobTitle.trim()) titlesMap["t0"] = { name: jobTitle.trim(), kind: "title" }; if (jobTitle.trim()) titlesMap["t0"] = { name: jobTitle.trim(), kind: "title" };
@@ -530,14 +551,20 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress
const mediaValue: Record<string, ContactMedia> | null | undefined = const mediaValue: Record<string, ContactMedia> | null | undefined =
Object.keys(mediaMap).length > 0 ? mediaMap : (hadMedia ? 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<ContactCard> = { const data: Partial<ContactCard> = {
name: { components: nameComponents, isOrdered: true }, name: nameValue,
...(kindValue ? { kind: kindValue } : {}),
nicknames: nickname.trim() ? { n0: { name: nickname.trim() } } : undefined, nicknames: nickname.trim() ? { n0: { name: nickname.trim() } } : undefined,
emails: Object.keys(emailsMap).length > 0 ? emailsMap : undefined, emails: Object.keys(emailsMap).length > 0 ? emailsMap : undefined,
phones: Object.keys(phonesMap).length > 0 ? phonesMap : undefined, phones: Object.keys(phonesMap).length > 0 ? phonesMap : undefined,
titles: Object.keys(titlesMap).length > 0 ? titlesMap : undefined, titles: Object.keys(titlesMap).length > 0 ? titlesMap : undefined,
organizations: organization.trim() organizations: orgName
? { o0: { name: organization.trim(), units: orgUnits } } ? { o0: { name: orgName, units: orgUnits } }
: undefined, : undefined,
addresses: Object.keys(addressesMap).length > 0 ? addressesMap : undefined, addresses: Object.keys(addressesMap).length > 0 ? addressesMap : undefined,
onlineServices: Object.keys(onlineServicesMap).length > 0 ? onlineServicesMap : 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() || ""; const previewEmail = emails.find(e => e.address.trim())?.address.trim() || "";
return ( return (
@@ -661,6 +688,47 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress
)} )}
<FormSection icon={User} title={t("section_identity")}> <FormSection icon={User} title={t("section_identity")}>
<div className="flex items-center gap-3">
<span className="text-xs text-muted-foreground">{t("contact_type")}</span>
<div role="radiogroup" aria-label={t("contact_type")} className="inline-flex gap-0.5 rounded-md border border-input p-0.5">
{[
{ org: false, label: t("type_person"), icon: User },
{ org: true, label: t("type_organization"), icon: Building },
].map(({ org, label, icon: Icon }) => (
<button
key={label}
type="button"
role="radio"
aria-checked={isOrg === org}
onClick={() => setIsOrg(org)}
className={cn(
"flex items-center gap-1.5 px-2.5 py-1 text-xs rounded transition-colors",
isOrg === org
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:text-foreground"
)}
>
<Icon className="w-3.5 h-3.5" />
{label}
</button>
))}
</div>
</div>
{isOrg ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label className="text-xs text-muted-foreground mb-1 block">
{t("organization")} <span className="text-red-500">*</span>
</label>
<Input value={organization} onChange={(e) => setOrganization(e.target.value)} placeholder={t("organization_placeholder")} autoFocus />
</div>
<div>
<label className="text-xs text-muted-foreground mb-1 block">{t("nickname")}</label>
<Input value={nickname} onChange={(e) => setNickname(e.target.value)} placeholder={t("nickname_placeholder")} />
</div>
</div>
) : (
<>
<div className="grid grid-cols-[auto_1fr_1fr_auto] gap-2"> <div className="grid grid-cols-[auto_1fr_1fr_auto] gap-2">
<div> <div>
<label className="text-xs text-muted-foreground mb-1 block">{t("prefix")}</label> <label className="text-xs text-muted-foreground mb-1 block">{t("prefix")}</label>
@@ -693,6 +761,8 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress
<Input value={nickname} onChange={(e) => setNickname(e.target.value)} placeholder={t("nickname_placeholder")} /> <Input value={nickname} onChange={(e) => setNickname(e.target.value)} placeholder={t("nickname_placeholder")} />
</div> </div>
</div> </div>
</>
)}
</FormSection> </FormSection>
{/* Email */} {/* Email */}
@@ -806,12 +876,15 @@ export function ContactForm({ contact, addressBooks, allKeywords, defaultAddress
</FormSection> </FormSection>
{/* Work & Organization */} {/* Work & Organization */}
<FormSection icon={Building} title={t("section_work")} collapsible defaultOpen={!!(organization || department || jobTitle || role)}> <FormSection icon={Building} title={t("section_work")} collapsible defaultOpen={!!((organization && !isOrg) || department || jobTitle || role)}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{/* In organization mode the org name is the card's identity, edited above. */}
{!isOrg && (
<div> <div>
<label className="text-xs text-muted-foreground mb-1 block">{t("organization")}</label> <label className="text-xs text-muted-foreground mb-1 block">{t("organization")}</label>
<Input value={organization} onChange={(e) => setOrganization(e.target.value)} placeholder={t("organization_placeholder")} /> <Input value={organization} onChange={(e) => setOrganization(e.target.value)} placeholder={t("organization_placeholder")} />
</div> </div>
)}
<div> <div>
<label className="text-xs text-muted-foreground mb-1 block">{t("department")}</label> <label className="text-xs text-muted-foreground mb-1 block">{t("department")}</label>
<Input value={department} onChange={(e) => setDepartment(e.target.value)} placeholder={t("department_placeholder")} /> <Input value={department} onChange={(e) => setDepartment(e.target.value)} placeholder={t("department_placeholder")} />
+42
View File
@@ -445,6 +445,48 @@ describe("generateVCard", () => {
const vcf = generateVCard([contact]); const vcf = generateVCard([contact]);
expect(vcf).toContain("NOTE:Has comma\\, semicolon\\; and newline\\nhere"); 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", () => { describe("round-trip: parse → generate → parse", () => {
+8 -2
View File
@@ -844,7 +844,9 @@ function buildContact(raw: Record<string, string[]>): ContactCard | null {
const hasName = card.name && (card.name.components?.length ?? 0) > 0 || !!card.name?.full; const hasName = card.name && (card.name.components?.length ?? 0) > 0 || !!card.name?.full;
const hasEmail = card.emails && Object.keys(card.emails).length > 0; 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; return card;
} }
@@ -882,7 +884,11 @@ function generateSingleVCard(contact: ContactCard): string {
const suffix = findKind("generation", "suffix"); const suffix = findKind("generation", "suffix");
const additional = findKind("given2", "additional", "middle"); 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) { if (fn) {
lines.push(`FN:${encodeValue(fn)}`); lines.push(`FN:${encodeValue(fn)}`);
lines.push(`N:${encodeValue(surname)};${encodeValue(given)};${encodeValue(additional)};${encodeValue(prefix)};${encodeValue(suffix)}`); lines.push(`N:${encodeValue(surname)};${encodeValue(given)};${encodeValue(additional)};${encodeValue(prefix)};${encodeValue(suffix)}`);
+4 -1
View File
@@ -2376,6 +2376,9 @@
"section_address_book": "الدليل", "section_address_book": "الدليل",
"select_address_book": "اختر دليلًا...", "select_address_book": "اختر دليلًا...",
"section_identity": "الاسم والهوية", "section_identity": "الاسم والهوية",
"contact_type": "نوع جهة الاتصال",
"type_person": "شخص",
"type_organization": "المؤسسة",
"section_work": "العمل والمؤسسة", "section_work": "العمل والمؤسسة",
"prefix": "اللقب", "prefix": "اللقب",
"prefix_placeholder": "د.، أ.، السيدة", "prefix_placeholder": "د.، أ.، السيدة",
@@ -2460,7 +2463,7 @@
"cancel": "إلغاء", "cancel": "إلغاء",
"creating": "جارٍ الإنشاء...", "creating": "جارٍ الإنشاء...",
"updating": "جارٍ التحديث...", "updating": "جارٍ التحديث...",
"name_required": "يلزم إدخال الاسم الأول أو اسم العائلة على الأقل", "name_required": "أدخل اسمًا أول أو اسم عائلة أو مؤسسة",
"email_invalid": "يرجى إدخال عنوان بريد إلكتروني صالح", "email_invalid": "يرجى إدخال عنوان بريد إلكتروني صالح",
"email_error_inline": "تنسيق البريد الإلكتروني غير صالح", "email_error_inline": "تنسيق البريد الإلكتروني غير صالح",
"save_failed": "فشل حفظ جهة الاتصال", "save_failed": "فشل حفظ جهة الاتصال",
+4 -1
View File
@@ -2376,6 +2376,9 @@
"section_address_book": "Directori", "section_address_book": "Directori",
"select_address_book": "Seleccioneu un directori...", "select_address_book": "Seleccioneu un directori...",
"section_identity": "Nom i identitat", "section_identity": "Nom i identitat",
"contact_type": "Tipus de contacte",
"type_person": "Persona",
"type_organization": "Organització",
"section_work": "Feina i organització", "section_work": "Feina i organització",
"prefix": "Prefix", "prefix": "Prefix",
"prefix_placeholder": "Dr., Sr., Sra.", "prefix_placeholder": "Dr., Sr., Sra.",
@@ -2460,7 +2463,7 @@
"cancel": "Cancel·la", "cancel": "Cancel·la",
"creating": "Creant...", "creating": "Creant...",
"updating": "Actualitzant...", "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_invalid": "Introduïu una adreça electrònica vàlida",
"email_error_inline": "Format de correu electrònic no vàlid", "email_error_inline": "Format de correu electrònic no vàlid",
"save_failed": "No s'ha pogut desar el contacte", "save_failed": "No s'ha pogut desar el contacte",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Adresář", "section_address_book": "Adresář",
"select_address_book": "Vyberte adresář...", "select_address_book": "Vyberte adresář...",
"section_identity": "Jméno a identita", "section_identity": "Jméno a identita",
"contact_type": "Typ kontaktu",
"type_person": "Osoba",
"type_organization": "Organizace",
"section_work": "Práce a organizace", "section_work": "Práce a organizace",
"prefix": "Titul", "prefix": "Titul",
"prefix_placeholder": "Dr., Pan, Paní", "prefix_placeholder": "Dr., Pan, Paní",
@@ -2459,7 +2462,7 @@
"cancel": "Zrušit", "cancel": "Zrušit",
"creating": "Vytváření...", "creating": "Vytváření...",
"updating": "Aktualizování...", "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_invalid": "Zadejte platnou e-mailovou adresu",
"email_error_inline": "Neplatný formát e-mailové adresy", "email_error_inline": "Neplatný formát e-mailové adresy",
"save_failed": "Uložení kontaktu selhalo", "save_failed": "Uložení kontaktu selhalo",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Adressebog", "section_address_book": "Adressebog",
"select_address_book": "Vælg en adressebog...", "select_address_book": "Vælg en adressebog...",
"section_identity": "Navn & identitet", "section_identity": "Navn & identitet",
"contact_type": "Kontakttype",
"type_person": "Person",
"type_organization": "Organisation",
"section_work": "Arbejde & organisation", "section_work": "Arbejde & organisation",
"prefix": "Præfiks", "prefix": "Præfiks",
"prefix_placeholder": "Dr., hr., fru", "prefix_placeholder": "Dr., hr., fru",
@@ -2459,7 +2462,7 @@
"cancel": "Annuller", "cancel": "Annuller",
"creating": "Opretter...", "creating": "Opretter...",
"updating": "Opdaterer...", "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_invalid": "Indtast en gyldig e-mailadresse",
"email_error_inline": "Ugyldigt e-mailformat", "email_error_inline": "Ugyldigt e-mailformat",
"save_failed": "Kunne ikke gemme kontakt", "save_failed": "Kunne ikke gemme kontakt",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Verzeichnis", "section_address_book": "Verzeichnis",
"select_address_book": "Verzeichnis auswählen...", "select_address_book": "Verzeichnis auswählen...",
"section_identity": "Name & Identität", "section_identity": "Name & Identität",
"contact_type": "Kontakttyp",
"type_person": "Person",
"type_organization": "Organisation",
"section_work": "Beruf & Organisation", "section_work": "Beruf & Organisation",
"prefix": "Anrede", "prefix": "Anrede",
"prefix_placeholder": "Dr., Herr, Frau", "prefix_placeholder": "Dr., Herr, Frau",
@@ -2459,7 +2462,7 @@
"cancel": "Abbrechen", "cancel": "Abbrechen",
"creating": "Wird erstellt...", "creating": "Wird erstellt...",
"updating": "Wird aktualisiert...", "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_invalid": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
"email_error_inline": "Ungültiges E-Mail-Format", "email_error_inline": "Ungültiges E-Mail-Format",
"save_failed": "Kontakt konnte nicht gespeichert werden", "save_failed": "Kontakt konnte nicht gespeichert werden",
+4 -1
View File
@@ -2376,6 +2376,9 @@
"section_address_book": "Directory", "section_address_book": "Directory",
"select_address_book": "Select a directory...", "select_address_book": "Select a directory...",
"section_identity": "Name & Identity", "section_identity": "Name & Identity",
"contact_type": "Contact type",
"type_person": "Person",
"type_organization": "Organization",
"section_work": "Work & Organization", "section_work": "Work & Organization",
"prefix": "Prefix", "prefix": "Prefix",
"prefix_placeholder": "Dr., Mr., Mrs.", "prefix_placeholder": "Dr., Mr., Mrs.",
@@ -2460,7 +2463,7 @@
"cancel": "Cancel", "cancel": "Cancel",
"creating": "Creating...", "creating": "Creating...",
"updating": "Updating...", "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_invalid": "Please enter a valid email address",
"email_error_inline": "Invalid email format", "email_error_inline": "Invalid email format",
"save_failed": "Failed to save contact", "save_failed": "Failed to save contact",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Directorio", "section_address_book": "Directorio",
"select_address_book": "Seleccionar un directorio...", "select_address_book": "Seleccionar un directorio...",
"section_identity": "Nombre e identidad", "section_identity": "Nombre e identidad",
"contact_type": "Tipo de contacto",
"type_person": "Persona",
"type_organization": "Organización",
"section_work": "Trabajo y organización", "section_work": "Trabajo y organización",
"prefix": "Prefijo", "prefix": "Prefijo",
"prefix_placeholder": "Dr., Sr., Sra.", "prefix_placeholder": "Dr., Sr., Sra.",
@@ -2459,7 +2462,7 @@
"cancel": "Cancelar", "cancel": "Cancelar",
"creating": "Creando...", "creating": "Creando...",
"updating": "Actualizando...", "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_invalid": "Introduce una dirección de correo válida",
"email_error_inline": "Formato de correo inválido", "email_error_inline": "Formato de correo inválido",
"save_failed": "Error al guardar el contacto", "save_failed": "Error al guardar el contacto",
+4 -1
View File
@@ -2376,6 +2376,9 @@
"section_address_book": "دفترچه", "section_address_book": "دفترچه",
"select_address_book": "انتخاب دفترچه...", "select_address_book": "انتخاب دفترچه...",
"section_identity": "نام و هویت", "section_identity": "نام و هویت",
"contact_type": "نوع مخاطب",
"type_person": "شخص",
"type_organization": "سازمان",
"section_work": "کار و سازمان", "section_work": "کار و سازمان",
"prefix": "پیشوند", "prefix": "پیشوند",
"prefix_placeholder": "دکتر، مهندس", "prefix_placeholder": "دکتر، مهندس",
@@ -2460,7 +2463,7 @@
"cancel": "انصراف", "cancel": "انصراف",
"creating": "در حال ایجاد...", "creating": "در حال ایجاد...",
"updating": "در حال به‌روزرسانی...", "updating": "در حال به‌روزرسانی...",
"name_required": "حداقل نام یا نام خانوادگی الزامی است", "name_required": "نام، نام خانوادگی یا سازمان را وارد کنید",
"email_invalid": "لطفاً یک آدرس ایمیل معتبر وارد کنید", "email_invalid": "لطفاً یک آدرس ایمیل معتبر وارد کنید",
"email_error_inline": "فرمت ایمیل نامعتبر است", "email_error_inline": "فرمت ایمیل نامعتبر است",
"save_failed": "ذخیره مخاطب ناموفق بود", "save_failed": "ذخیره مخاطب ناموفق بود",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Répertoire", "section_address_book": "Répertoire",
"select_address_book": "Sélectionner un répertoire...", "select_address_book": "Sélectionner un répertoire...",
"section_identity": "Nom et identité", "section_identity": "Nom et identité",
"contact_type": "Type de contact",
"type_person": "Personne",
"type_organization": "Organisation",
"section_work": "Travail et organisation", "section_work": "Travail et organisation",
"prefix": "Préfixe", "prefix": "Préfixe",
"prefix_placeholder": "Dr., M., Mme", "prefix_placeholder": "Dr., M., Mme",
@@ -2459,7 +2462,7 @@
"cancel": "Annuler", "cancel": "Annuler",
"creating": "Création...", "creating": "Création...",
"updating": "Mise à jour...", "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_invalid": "Veuillez saisir une adresse e-mail valide",
"email_error_inline": "Format d'e-mail invalide", "email_error_inline": "Format d'e-mail invalide",
"save_failed": "Échec de l'enregistrement du contact", "save_failed": "Échec de l'enregistrement du contact",
+4 -1
View File
@@ -2289,6 +2289,9 @@
"section_address_book": "ספרייה", "section_address_book": "ספרייה",
"select_address_book": "בחר ספרייה...", "select_address_book": "בחר ספרייה...",
"section_identity": "שם וזהות", "section_identity": "שם וזהות",
"contact_type": "סוג איש קשר",
"type_person": "אדם",
"type_organization": "ארגון",
"section_work": "עבודה וארגון", "section_work": "עבודה וארגון",
"prefix": "קידומת", "prefix": "קידומת",
"prefix_placeholder": "ד\"ר, מר, גברת.", "prefix_placeholder": "ד\"ר, מר, גברת.",
@@ -2373,7 +2376,7 @@
"cancel": "לְבַטֵל", "cancel": "לְבַטֵל",
"creating": "יוצר...", "creating": "יוצר...",
"updating": "מעדכן...", "updating": "מעדכן...",
"name_required": "נדרש לפחות שם פרטי או שם משפחה", "name_required": "יש להזין שם פרטי, שם משפחה או ארגון",
"email_invalid": "נא להזין כתובת אימייל חוקית", "email_invalid": "נא להזין כתובת אימייל חוקית",
"email_error_inline": "פורמט אימייל לא חוקי", "email_error_inline": "פורמט אימייל לא חוקי",
"save_failed": "שמירת איש הקשר נכשלה", "save_failed": "שמירת איש הקשר נכשלה",
+4 -1
View File
@@ -2376,6 +2376,9 @@
"section_address_book": "Címtár", "section_address_book": "Címtár",
"select_address_book": "Címtár kiválasztása...", "select_address_book": "Címtár kiválasztása...",
"section_identity": "Név és azonosság", "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", "section_work": "Munka és szervezet",
"prefix": "Előtag", "prefix": "Előtag",
"prefix_placeholder": "Dr., Úr., Mrs.", "prefix_placeholder": "Dr., Úr., Mrs.",
@@ -2460,7 +2463,7 @@
"cancel": "Mégse", "cancel": "Mégse",
"creating": "Létrehozás...", "creating": "Létrehozás...",
"updating": "Frissíté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_invalid": "Kérjük, adj meg egy érvényes e-mail címet",
"email_error_inline": "Érvénytelen e-mail formátum", "email_error_inline": "Érvénytelen e-mail formátum",
"save_failed": "Nem sikerült menteni a névjegyet", "save_failed": "Nem sikerült menteni a névjegyet",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Rubrica", "section_address_book": "Rubrica",
"select_address_book": "Seleziona una rubrica...", "select_address_book": "Seleziona una rubrica...",
"section_identity": "Nome e identità", "section_identity": "Nome e identità",
"contact_type": "Tipo di contatto",
"type_person": "Persona",
"type_organization": "Organizzazione",
"section_work": "Lavoro e organizzazione", "section_work": "Lavoro e organizzazione",
"prefix": "Prefisso", "prefix": "Prefisso",
"prefix_placeholder": "Dott., Sig., Sig.ra", "prefix_placeholder": "Dott., Sig., Sig.ra",
@@ -2459,7 +2462,7 @@
"cancel": "Annulla", "cancel": "Annulla",
"creating": "Creazione...", "creating": "Creazione...",
"updating": "Aggiornamento...", "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_invalid": "Inserisci un indirizzo email valido",
"email_error_inline": "Formato email non valido", "email_error_inline": "Formato email non valido",
"save_failed": "Impossibile salvare il contatto", "save_failed": "Impossibile salvare il contatto",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "ディレクトリ", "section_address_book": "ディレクトリ",
"select_address_book": "ディレクトリを選択...", "select_address_book": "ディレクトリを選択...",
"section_identity": "名前と識別情報", "section_identity": "名前と識別情報",
"contact_type": "連絡先の種類",
"type_person": "個人",
"type_organization": "組織",
"section_work": "職業と組織", "section_work": "職業と組織",
"prefix": "敬称", "prefix": "敬称",
"prefix_placeholder": "博士、氏", "prefix_placeholder": "博士、氏",
@@ -2459,7 +2462,7 @@
"cancel": "キャンセル", "cancel": "キャンセル",
"creating": "作成中...", "creating": "作成中...",
"updating": "更新中...", "updating": "更新中...",
"name_required": "名前は必須です", "name_required": "名、姓、または組織を入力してください",
"email_invalid": "有効なメールアドレスを入力してください", "email_invalid": "有効なメールアドレスを入力してください",
"email_error_inline": "メールアドレスの形式が正しくありません", "email_error_inline": "メールアドレスの形式が正しくありません",
"save_failed": "連絡先の保存に失敗しました", "save_failed": "連絡先の保存に失敗しました",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "디렉터리", "section_address_book": "디렉터리",
"select_address_book": "디렉터리 선택...", "select_address_book": "디렉터리 선택...",
"section_identity": "이름 및 신원", "section_identity": "이름 및 신원",
"contact_type": "연락처 유형",
"type_person": "개인",
"type_organization": "소속(회사)",
"section_work": "직장 및 소속", "section_work": "직장 및 소속",
"prefix": "호칭", "prefix": "호칭",
"prefix_placeholder": "예: Dr., Mr., Mrs.", "prefix_placeholder": "예: Dr., Mr., Mrs.",
@@ -2459,7 +2462,7 @@
"cancel": "취소", "cancel": "취소",
"creating": "만드는 중...", "creating": "만드는 중...",
"updating": "업데이트 중...", "updating": "업데이트 중...",
"name_required": "이름이나중에 하나는 꼭 필요해요", "name_required": "이름,또는 조직을 입력하세요",
"email_invalid": "올바른 이메일 주소를 입력해 주세요", "email_invalid": "올바른 이메일 주소를 입력해 주세요",
"email_error_inline": "이메일 형식이 잘못되었어요", "email_error_inline": "이메일 형식이 잘못되었어요",
"save_failed": "연락처를 저장하지 못했어요", "save_failed": "연락처를 저장하지 못했어요",
+4 -1
View File
@@ -2371,6 +2371,9 @@
"section_address_book": "Katalogs", "section_address_book": "Katalogs",
"select_address_book": "Izvēlieties katalogu...", "select_address_book": "Izvēlieties katalogu...",
"section_identity": "Vārds un identitāte", "section_identity": "Vārds un identitāte",
"contact_type": "Kontakta veids",
"type_person": "Persona",
"type_organization": "Organizācija",
"section_work": "Darbs un organizācija", "section_work": "Darbs un organizācija",
"prefix": "Prefikss", "prefix": "Prefikss",
"prefix_placeholder": "Dr., kungs, kundze", "prefix_placeholder": "Dr., kungs, kundze",
@@ -2455,7 +2458,7 @@
"cancel": "Atcelt", "cancel": "Atcelt",
"creating": "Izveido...", "creating": "Izveido...",
"updating": "Atjaunina...", "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_invalid": "Ievadiet derīgu e-pasta adresi",
"email_error_inline": "Nederīgs e-pasta formāts", "email_error_inline": "Nederīgs e-pasta formāts",
"save_failed": "Neizdevās saglabāt kontaktu", "save_failed": "Neizdevās saglabāt kontaktu",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Adresboek", "section_address_book": "Adresboek",
"select_address_book": "Selecteer een adresboek...", "select_address_book": "Selecteer een adresboek...",
"section_identity": "Naam en identiteit", "section_identity": "Naam en identiteit",
"contact_type": "Contacttype",
"type_person": "Persoon",
"type_organization": "Organisatie",
"section_work": "Werk en organisatie", "section_work": "Werk en organisatie",
"prefix": "Voorvoegsel", "prefix": "Voorvoegsel",
"prefix_placeholder": "Dr., Dhr., Mevr.", "prefix_placeholder": "Dr., Dhr., Mevr.",
@@ -2459,7 +2462,7 @@
"cancel": "Annuleren", "cancel": "Annuleren",
"creating": "Aanmaken...", "creating": "Aanmaken...",
"updating": "Bijwerken...", "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_invalid": "Voer een geldig e-mailadres in",
"email_error_inline": "Ongeldig e-mailformaat", "email_error_inline": "Ongeldig e-mailformaat",
"save_failed": "Kon contact niet opslaan", "save_failed": "Kon contact niet opslaan",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Katalog", "section_address_book": "Katalog",
"select_address_book": "Wybierz katalog...", "select_address_book": "Wybierz katalog...",
"section_identity": "Imię i tożsamość", "section_identity": "Imię i tożsamość",
"contact_type": "Typ kontaktu",
"type_person": "Osoba",
"type_organization": "Organizacja",
"section_work": "Praca i organizacja", "section_work": "Praca i organizacja",
"prefix": "Tytuł", "prefix": "Tytuł",
"prefix_placeholder": "Dr, Pan, Pani", "prefix_placeholder": "Dr, Pan, Pani",
@@ -2459,7 +2462,7 @@
"cancel": "Anuluj", "cancel": "Anuluj",
"creating": "Tworzenie...", "creating": "Tworzenie...",
"updating": "Aktualizowanie...", "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_invalid": "Wprowadź prawidłowy adres e-mail",
"email_error_inline": "Nieprawidłowy format adresu e-mail", "email_error_inline": "Nieprawidłowy format adresu e-mail",
"save_failed": "Nie udało się zapisać kontaktu", "save_failed": "Nie udało się zapisać kontaktu",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Diretório", "section_address_book": "Diretório",
"select_address_book": "Selecionar um diretório...", "select_address_book": "Selecionar um diretório...",
"section_identity": "Nome e identidade", "section_identity": "Nome e identidade",
"contact_type": "Tipo de contacto",
"type_person": "Pessoa",
"type_organization": "Organização",
"section_work": "Trabalho e organização", "section_work": "Trabalho e organização",
"prefix": "Prefixo", "prefix": "Prefixo",
"prefix_placeholder": "Dr., Sr., Sra.", "prefix_placeholder": "Dr., Sr., Sra.",
@@ -2459,7 +2462,7 @@
"cancel": "Cancelar", "cancel": "Cancelar",
"creating": "Criando...", "creating": "Criando...",
"updating": "Atualizando...", "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_invalid": "Por favor, insira um endereço de e-mail válido",
"email_error_inline": "Formato de e-mail inválido", "email_error_inline": "Formato de e-mail inválido",
"save_failed": "Falha ao salvar contato", "save_failed": "Falha ao salvar contato",
+4 -1
View File
@@ -2376,6 +2376,9 @@
"section_address_book": "Director", "section_address_book": "Director",
"select_address_book": "Selectați un director...", "select_address_book": "Selectați un director...",
"section_identity": "Nume și identitate", "section_identity": "Nume și identitate",
"contact_type": "Tip de contact",
"type_person": "Persoană",
"type_organization": "Organizare",
"section_work": "Muncă și organizare", "section_work": "Muncă și organizare",
"prefix": "Prefix", "prefix": "Prefix",
"prefix_placeholder": "Dr., Dl., Dna.", "prefix_placeholder": "Dr., Dl., Dna.",
@@ -2460,7 +2463,7 @@
"cancel": "Anulează", "cancel": "Anulează",
"creating": "Se creează...", "creating": "Se creează...",
"updating": "Se actualizează...", "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_invalid": "Vă rugăm să introduceți o adresă de e-mail validă",
"email_error_inline": "Format de e-mail nevalid", "email_error_inline": "Format de e-mail nevalid",
"save_failed": "Nu s-a putut salva contactul", "save_failed": "Nu s-a putut salva contactul",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Каталог", "section_address_book": "Каталог",
"select_address_book": "Выберите каталог...", "select_address_book": "Выберите каталог...",
"section_identity": "Имя и личность", "section_identity": "Имя и личность",
"contact_type": "Тип контакта",
"type_person": "Человек",
"type_organization": "Организация",
"section_work": "Работа и организация", "section_work": "Работа и организация",
"prefix": "Префикс", "prefix": "Префикс",
"prefix_placeholder": "Д-р., Г-н., Г-жа.", "prefix_placeholder": "Д-р., Г-н., Г-жа.",
@@ -2459,7 +2462,7 @@
"cancel": "Отмена", "cancel": "Отмена",
"creating": "Создание...", "creating": "Создание...",
"updating": "Обновление...", "updating": "Обновление...",
"name_required": "Требуется хотя бы имя или фамилия", "name_required": "Укажите имя, фамилию или организацию",
"email_invalid": "Введите корректный адрес электронной почты", "email_invalid": "Введите корректный адрес электронной почты",
"email_error_inline": "Неверный формат email", "email_error_inline": "Неверный формат email",
"save_failed": "Не удалось сохранить контакт", "save_failed": "Не удалось сохранить контакт",
+4 -1
View File
@@ -2376,6 +2376,9 @@
"section_address_book": "Adresár", "section_address_book": "Adresár",
"select_address_book": "Vyberte adresár...", "select_address_book": "Vyberte adresár...",
"section_identity": "Meno a identita", "section_identity": "Meno a identita",
"contact_type": "Typ kontaktu",
"type_person": "Osoba",
"type_organization": "Organizácia",
"section_work": "Práca a organizácia", "section_work": "Práca a organizácia",
"prefix": "Titul", "prefix": "Titul",
"prefix_placeholder": "Dr., Pán, Pani", "prefix_placeholder": "Dr., Pán, Pani",
@@ -2460,7 +2463,7 @@
"cancel": "Zrušiť", "cancel": "Zrušiť",
"creating": "Vytváranie...", "creating": "Vytváranie...",
"updating": "Aktualizovanie...", "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_invalid": "Zadajte platnú e-mailovú adresu",
"email_error_inline": "Neplatný formát e-mailovej adresy", "email_error_inline": "Neplatný formát e-mailovej adresy",
"save_failed": "Uloženie kontaktu zlyhalo", "save_failed": "Uloženie kontaktu zlyhalo",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Dizin", "section_address_book": "Dizin",
"select_address_book": "Bir dizin seçin...", "select_address_book": "Bir dizin seçin...",
"section_identity": "Ad ve Kimlik", "section_identity": "Ad ve Kimlik",
"contact_type": "Kişi türü",
"type_person": "Kişi",
"type_organization": "Kuruluş",
"section_work": "İş ve Kuruluş", "section_work": "İş ve Kuruluş",
"prefix": "Ön Ek", "prefix": "Ön Ek",
"prefix_placeholder": "Dr., Bay, Bayan", "prefix_placeholder": "Dr., Bay, Bayan",
@@ -2459,7 +2462,7 @@
"cancel": "İptal", "cancel": "İptal",
"creating": "Oluşturuluyor...", "creating": "Oluşturuluyor...",
"updating": "Güncelleniyor...", "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_invalid": "Lütfen geçerli bir e-posta adresi girin",
"email_error_inline": "Geçersiz e-posta biçimi", "email_error_inline": "Geçersiz e-posta biçimi",
"save_failed": "Kişi kaydedilemedi", "save_failed": "Kişi kaydedilemedi",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "Довідник", "section_address_book": "Довідник",
"select_address_book": "Виберіть каталог...", "select_address_book": "Виберіть каталог...",
"section_identity": "Ім'я та ідентифікація", "section_identity": "Ім'я та ідентифікація",
"contact_type": "Тип контакту",
"type_person": "Людина",
"type_organization": "організація",
"section_work": "Робота та організація", "section_work": "Робота та організація",
"prefix": "Префікс", "prefix": "Префікс",
"prefix_placeholder": "доктор, пан, місіс", "prefix_placeholder": "доктор, пан, місіс",
@@ -2459,7 +2462,7 @@
"cancel": "Скасувати", "cancel": "Скасувати",
"creating": "Створення...", "creating": "Створення...",
"updating": "Оновлення...", "updating": "Оновлення...",
"name_required": "Потрібне принаймні ім’я або прізвище", "name_required": "Вкажіть ім'я, прізвище або організацію",
"email_invalid": "Введіть дійсну електронну адресу", "email_invalid": "Введіть дійсну електронну адресу",
"email_error_inline": "Недійсний формат електронної пошти", "email_error_inline": "Недійсний формат електронної пошти",
"save_failed": "Не вдалося зберегти контакт", "save_failed": "Не вдалося зберегти контакт",
+4 -1
View File
@@ -2375,6 +2375,9 @@
"section_address_book": "地址簿", "section_address_book": "地址簿",
"select_address_book": "选择地址簿...", "select_address_book": "选择地址簿...",
"section_identity": "姓名和身份", "section_identity": "姓名和身份",
"contact_type": "联系人类型",
"type_person": "个人",
"type_organization": "组织",
"section_work": "工作与组织", "section_work": "工作与组织",
"prefix": "前缀", "prefix": "前缀",
"prefix_placeholder": "博士、先生、女士", "prefix_placeholder": "博士、先生、女士",
@@ -2459,7 +2462,7 @@
"cancel": "取消", "cancel": "取消",
"creating": "创建中...", "creating": "创建中...",
"updating": "更新中...", "updating": "更新中...",
"name_required": "至少需要名字姓氏", "name_required": "请输入名字姓氏或组织",
"email_invalid": "请输入有效的邮箱地址", "email_invalid": "请输入有效的邮箱地址",
"email_error_inline": "邮箱地址格式无效", "email_error_inline": "邮箱地址格式无效",
"save_failed": "保存联系人失败", "save_failed": "保存联系人失败",