A PHP library for creating personalized salutations for different contexts with support for titles, gender, and multilingual translations.
composer require heimrichhannot/salutation-creator- PHP ^8.2
- symfony/translation-contracts ^1 || ^2 || ^3
- ✅ Flexible salutation generation (formal/informal)
- ✅ Support for academic titles (Dr., Prof., etc.)
- ✅ Gender-sensitive salutations
- ✅ Prefix and suffix titles with priorities
- ✅ Name parsing from strings and arrays
- ✅ Multilingual translations via Symfony Translation
- ✅ Extensible with custom name, gender, and title classes
Basic example:
use HeimrichHannot\SalutationCreator\SalutationCreator;
use HeimrichHannot\SalutationCreator\SalutationContext;
use HeimrichHannot\SalutationCreator\Context\Name\GermanTypeName;
use HeimrichHannot\SalutationCreator\Context\Gender\Gender;
$salutationCreator = new SalutationCreator($translator);
$context = (new SalutationContext())
->setName(new GermanTypeName('Max', 'Mustermann'))
->setGender(Gender::MALE);
echo $salutationCreator->generate($context);
// Output: "Sehr geehrter Herr Mustermann"With (academic) Titles:
use HeimrichHannot\SalutationCreator\Context\Title\GermanDoctorTitle;
use HeimrichHannot\SalutationCreator\Context\Title\GermanProfessorTitle;
use HeimrichHannot\SalutationCreator\Context\Title\PrefixTitle;
use HeimrichHannot\SalutationCreator\Helper\Titles;
$context = (new SalutationContext())
->setName(new GermanTypeName('Anna', 'Schmidt'))
->setGender(Gender::FEMALE)
->addTitle(new GermanDoctorTitle())
->addTitle(new GermanProfessorTitle())
->addTitle(new PrefixTitle('Hero'))
->addTitles(Titles::fromString('dr phd unknown')); // Adding titles from string
echo $salutationCreator->generate($context);
// Output: "Sehr geehrte Frau Prof. Dr. Hero Dr. Schmidt PhD."Custom translation key (e.g. informal salutation):
$context = (new SalutationContext())
->setName(new GermanTypeName('Lisa', 'Müller'))
->setTranslationConfig('format.informal');
echo $salutationCreator->generate($context);
// Output: "Hallo Lisa"Gender from string:
use HeimrichHannot\SalutationCreator\SalutationCreator;
use HeimrichHannot\SalutationCreator\SalutationContext;
use HeimrichHannot\SalutationCreator\Context\Gender\Gender;
use HeimrichHannot\SalutationCreator\Context\Name\GermanTypeName;
$data = [
'firstname' => 'Lisa',
'lastname' => 'Müller',
'gender' => 'female',
];
$salutationContext = (new SalutationContext())
->setName(new GermanTypeName($data['firstname'], $data['lastname']))
->setGender(Gender::tryFromString($data['gender']));
echo (new SalutationCreator($this->translator))->generate($salutationContext);
// Outputs "Sehr geehrte Frau Müller"Use the bundled name parsers to create AbstractName instances from strings or arrays before passing them to a SalutationContext.
use HeimrichHannot\SalutationCreator\Context\Name\GermanTypeName;
use HeimrichHannot\SalutationCreator\Parser\Name\StringParser;
$name = StringParser::create(GermanTypeName::class)->parse('Max Mustermann');
echo $name?->getFullName();
// Output: "Max Mustermann"The string parser supports common formats such as Max Mustermann and Mustermann, Max. For single-part names, parsing returns null by default because the result is incomplete.
If incomplete names are acceptable for your use case, enable them explicitly:
$name = StringParser::create(GermanTypeName::class)
->allowIncomplete(true)
->parse('Mustermann');
echo $name?->getFormalName();
// Output: "Mustermann"use HeimrichHannot\SalutationCreator\Context\Name\GermanTypeName;
use HeimrichHannot\SalutationCreator\Parser\Name\ArrayParser;
$name = ArrayParser::create(GermanTypeName::class)->parse([
'firstname' => 'Max',
'lastname' => 'Mustermann',
]);
echo $name?->getFullName();
// Output: "Max Mustermann"For GermanTypeName, the array parser supports English keys (firstname, lastname, name) and German keys (vorname, nachname, zuname, name). Non-string values are ignored.
Since this library in framework-agnostic, you need to register the translation files manually or copy them to your project's translation directory.
You'll find translation files in the symfony translation ICU format in the translations directory of this package.
In your config/packages/framework.yaml:
framework:
translator:
paths:
- '%kernel.project_dir%/vendor/heimrichhannot/salutation-creator/translations'use HeimrichHannot\SalutationCreator\SalutationCreator;
use HeimrichHannot\SalutationCreator\SalutationContext;
use HeimrichHannot\SalutationCreator\Context\Name\GermanTypeName;
use Symfony\Contracts\Translation\TranslatorInterface;
class MessageController
{
public function __construct(
private readonly TranslatorInterface $translator,
) {}
public function sendMessage($firstName, $lastName): string
{
$salutation = (new SalutationCreator($this->translator))
->generate(
(new SalutationContext())
->setName(new GermanTypeName($firstName, $lastName))
);
return $salutation;
}
}Create a translation file translations/salutation_creator+intl-icu.{locale}.yaml:
format.formal: >
{gender, select,
m {Sehr geehrter {gender_name} {prefix_titles} {formal_name} {suffix_titles}}
w {Sehr geehrte {gender_name} {prefix_titles} {formal_name} {suffix_titles}}
nb {Sehr geehrte*r {gender_name} {prefix_titles} {formal_name} {suffix_titles}}
other {Sehr geehrter {gender_name} {prefix_titles} {formal_name} {suffix_titles}}
}
format.informal: 'Hallo {given_name}'
gender.m: 'Herr'
gender.f: 'Frau'
gender.d: ''
gender.nb: ''
title.german_doctor: 'Dr.'
title.german_professor: 'Prof.'Note: The example and the bundles translation files follow the Symfony ICU MessageFormat,
which supports the select syntax for conditional translations based on variables like gender. This allows for flexible, gender-sensitive formatting.
$context->setTranslationConfig(
formatKey: 'my_custom.format',
domain: 'my_domain'
);The following parameters are available in translations:
%prefix_titles%- All titles before the name%suffix_titles%- All titles after the name%gender_name%- Gender designation (Mr./Ms.)%gender%- Gender abbreviation (m/f/d)%name%- Full name%formal_name%- Formal name (usually last name)%given_name%- First name%has_name%- Containing yes if a name is available, otherwise no. Can be used for conditional translations based on the presence of a name.
GermanTypeName: German names with first and last name
StringParser: Parses full names from plain stringsArrayParser: Parses first and last names from array data
Gender::MALE- MaleGender::FEMALE- FemaleGender::DIVERSE- Diverse
GermanDoctorTitle: Doctor title (Dr.)GermanProfessorTitle: Professor title (Prof.)PrefixTitle: Custom title before the nameSuffixTitle: Custom title after the name
use HeimrichHannot\SalutationCreator\Context\Title\PrefixTitle;
$context->addTitle(new PrefixTitle(
message: 'Dr. med.',
priority: 5
));You can create custom gender classes by implementing the GenderInterface:
use HeimrichHannot\SalutationCreator\Context\Gender\GenderInterface;
use HeimrichHannot\SalutationCreator\SalutationPartResult;
enum CustomGender: string implements GenderInterface
{
case NON_BINARY = 'nb';
case PREFER_NOT_TO_SAY = 'x';
public function build(): SalutationPartResult
{
return new SalutationPartResult(
'gender.' . $this->value,
domain: 'my_custom_domain',
fallback: ucfirst($this->value),
);
}
public function getKey(): string
{
return $this->value;
}
}You can create custom title classes by extending AbstractTitle:
use HeimrichHannot\SalutationCreator\Context\Title\AbstractTitle;
use HeimrichHannot\SalutationCreator\Context\Position;
use HeimrichHannot\SalutationCreator\SalutationPartResult;
class CustomTitle extends AbstractTitle
{
public function build(): SalutationPartResult
{
return new SalutationPartResult(
message: 'title.custom',
domain: 'my_custom_domain',
fallback: 'Custom Title'
);
}
public function getPosition(): Position
{
return Position::PREFIX;
}
public function getPriority(): int
{
return 10; // Higher priority = appears first
}
}use HeimrichHannot\SalutationCreator\Context\Name\AbstractName;
class IcelandTypeName extends AbstractName
{
public function __construct(
private readonly string $firstName,
private readonly string $lastName
) {}
public function getFullName(): string
{
return $this->firstName . ' ' . $this->lastName;
}
public function getFormalName(): string
{
return $this->firstName;
}
public function getGivenName(): string
{
return $this->firstName;
}
}