mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 01:54:10 +00:00
AppLocale : proposer le français
Ajoute 'fr' à AppLocale et à framework.enabled_locales, qui doivent rester synchronisés (le commentaire de translation.yaml le dit). Le sélecteur de langue des réglages et celui de l'installation lisent AppLocale::cases() : « 🇫🇷 Français » y apparaît sans autre modification. Deux réglages suivent la langue et devaient connaître le français : - ClockFormat::forLocale() : le français écrit 14 h 00, donc 24 heures par défaut comme l'allemand. Sans ça, un utilisateur francophone qui n'a rien choisi verrait « 2 PM ». Testé dans ClockFormatResolverTest. - Le sélecteur d'emoji charge ses noms et mots-clés par langue (la recherche « cœur » ne trouve rien dans les données anglaises). assets/emoji/fr/data.json vient de emoji-picker-element-data, comme en/ et de/. J'ai vérifié que ces deux fichiers sont identiques octet pour octet à ceux publiés par le paquet, donc fr/ vient bien de la même source. Le gabarit choisit maintenant de, fr ou en d'après la langue au lieu du seul test « starts with 'de' ». Laissé tel quel : DeterministicDateDetector, qui lit les dates dans le contenu des mails selon leur langue, et non la langue de l'interface. Apprendre les dates françaises (« le 4 août ») est un autre chantier. Docs : README, features/appearance.md (et sa traduction allemande, avec le hash mis à jour), et une entrée de CHANGELOG. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018nYWPc4bWacb79hm4ytfni
This commit is contained in:
1 parent
32f6926df8
commit
5550727888
11 files changed
+24
-8
No files matched your search
@@ -6,6 +6,13 @@ so anything that changes the schema irreversibly is called out explicitly.
|
||||
The published image tags: `latest` follows the most recent release below,
|
||||
`main` follows the tip of the default branch, and `sha-…` pins one commit.
|
||||
|
||||
## Unreleased
|
||||
|
||||
**plMail speaks French.** Choose **Français** under **Settings → General → Language**, or when
|
||||
creating the first account. The whole interface is translated, including the admin panel, the
|
||||
validation and sign-in messages and the emoji picker's search; French also defaults to the
|
||||
24-hour clock, as German does. No migration.
|
||||
|
||||
## v0.3.5 — 2026-10-13
|
||||
|
||||
**Browsers and password managers kept offering your logins in client-secret fields.** Opening
|
||||
|
||||
@@ -80,7 +80,7 @@ provider doesn't.
|
||||
with what changed since the one they run.
|
||||
- **Yours** — two-factor auth, sign-in through Authentik, Keycloak or any other OpenID Connect
|
||||
provider, per-app JMAP passwords, 41 themes, ten icons for the tab and the phone's home screen,
|
||||
English, German and Pirate.
|
||||
English, German, French and Pirate.
|
||||
|
||||
## A look around
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
[{"shortcodes":["visage_rieur"],"annotation":"visage rieur","tags":["content","dents","grand sourire","heureux","joyeux","rire","sourire","super","visage","visage très souriant","émoticône"],"emoji":"😀","order":1,"group":0,"version":1},{"shortcodes":["visage_souriant_avec_de_grands_yeux"],"annotation":"visage souriant avec de grands yeux","tags":["content","dents","formidable","grand sourire","heureux","sourire","super","visage très souriant avec la bouche ouverte","émoticône"],"emoji":"😃","order":2,"group":0,"version":0.6},{"shortcodes":["visage_tres_souriant_aux_yeux_rieurs"],"annotation":"visage très souriant aux yeux rieurs","tags":["content","grand sourire","heureux","joyeux","lol","mdr","rire","sourire","sourire mignon","visage avec la bouche ouverte et les yeux souriants","émoticône"],"emoji":"😄","order":3,"group":0,"version":0.6,"emoticon":":D"},{"shortcodes":["visage_souriant_aux_yeux_rieurs"],"annotation":"visage souriant aux yeux rieurs","tags":["content","dents","grand sourire","heureux","sourire","sourire de toutes ses dents","super","visage","visage souriant avec yeux souriants","yeux souriants","émoticône"],"emoji":"😁","order":4,"group":0,"version":0.6},{"shortcodes":["visage_souriant_avec_yeux_plisses"],"annotation":"visage souriant avec yeux plissés","tags":["ahah","bouche","content","lol","mdr","rire","sourire","visage","visage souriant avec bouche ouverte et yeux fermés","émoticône"],"emoji":"😆","order":5,"group":0,"version":0.6,"emoticon":"XD"},{"shortcodes":["visage_souriant_avec_une_goutte_de_sueur"],"annotation":"visage souriant avec une goutte de sueur","tags":["excité","froid","goutte","sourire","sourire nerveux","stressé","sueur","sueurs froides","émoticône"],"emoji":"😅","order":6,"group":0,"version":0.6},{"shortcodes":["se_rouler_par_terre_de_rire"],"annotation":"se rouler par terre de rire","tags":["content","drôle","hilare","joyeux","larmes","lol","mdr","pleurer de rire","rire","rire aux larmes","se rouler par terre","se tordre de rire","visage","visage qui rit aux larmes","émoticône"],"emoji":"🤣","order":7,"group":0,"version":3,"emoticon":":'D"},{"shortcodes":["visage_riant_aux_larmes"],"annotation":"visage riant aux larmes","tags":["content","heureux","joie","larmes","lol","mdr","pleurer de joie","pleurer de rire","rire aux larmes","sourire","émoticône"],"emoji":"😂","order":8,"group":0,"version":0.6,"emoticon":":')"},{"shortcodes":["visage_avec_un_leger_sourire"],"annotation":"visage avec un léger sourire","tags":["content","heureux","léger sourire","sourire","visage souriant","émoticône"],"emoji":"🙂","order":9,"group":0,"version":1,"emoticon":":)"},{"shortcodes":["tete_a_lenvers"],"annotation":"tête à l’envers","tags":["incroyable","renversée","tête","visage","émoticône"],"emoji":"🙃","order":10,"group":0,"version":1},{"shortcodes":["visage_qui_fond"],"annotation":"visage qui fond","tags":["disparaître","fondre","gêne","liquide","liquéfié","sarcasme","se dissoudre"],"emoji":"🫠","order":11,"group":0,"version":14},{"shortcodes":["visage_faisant_un_clin_doeil"],"annotation":"visage faisant un clin d’œil","tags":["blague","clin d’œil","flirter","plaisanter","sexy","visage","émoticône"],"emoji":"😉","order":12,"group":0,"version":0.6,"emoticon":";)"},{"shortcodes":["visage_souriant_avec_yeux_rieurs"],"annotation":"visage souriant avec yeux rieurs","tags":["content","heureux","rougir","satisfait","sourire","visage","visage qui rougit","visage souriant avec yeux souriants","yeux","émoticône","œil"],"emoji":"😊","order":13,"group":0,"version":0.6,"emoticon":":>"},{"shortcodes":["visage_souriant_avec_aureole"],"annotation":"visage souriant avec auréole","tags":["ange","angélique","auréole","bible","béni","heureux","innocent","paix","sourire","spirituel","émoticône"],"emoji":"😇","order":14,"group":0,"version":1,"emoticon":"O:)"},{"shortcodes":["visage_souriant_avec_coeurs"],"annotation":"visage souriant avec cœurs","tags":["adorer","aimer","amour","béguin","coeur","cœurs","je t’aime","j’adore","smiley avec 3 coeurs","sourire"],"emoji":"🥰","order":15,"group":0,"version":11},{"shortcodes":["visage_souriant_avec_yeux_en_forme_de_coeur"],"annotation":"visage souriant avec yeux en forme de cœur","tags":["amour","amoureux","cœur","romantique","sourire","visage","yeux","émoticône","œil"],"emoji":"😍","order":16,"group":0,"version":0.6},{"shortcodes":["visage_avec_des_etoiles_a_la_place_des_yeux"],"annotation":"visage avec des étoiles à la place des yeux","tags":["grand sourire","heureux","pressé","surexcité","tête avec étoiles","visage","wow","yeux","étoile","étoiles dans les yeux"],"emoji":"🤩","order":17,"group":0,"version":5},{"shortcodes":["visage_envoyant_un_bisou"],"annotation":"visage envoyant un bisou","tags":["amour","baiser","bise","bisou et cœur","bonjour","bonne nuit","cœur","embrasser","je t’aime","visage qui fait une bise avec un cœur","émoticône"],"Line truncated
|
||||
@@ -1,7 +1,7 @@
|
||||
framework:
|
||||
default_locale: en
|
||||
# Keep in sync with App\Domain\Enum\AppLocale and translations/messages.*.yaml.
|
||||
enabled_locales: ['en', 'de', 'en_PI']
|
||||
enabled_locales: ['en', 'de', 'fr', 'en_PI']
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
providers:
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- translated-from: features/appearance.md sha1:43f57642347736db71ec013bef9e22bfb70ee747 -->
|
||||
<!-- translated-from: features/appearance.md sha1:963b5496c55b39c78ef14815a4c3c0e5b2ae8f78 -->
|
||||
|
||||
# Darstellung
|
||||
|
||||
@@ -281,7 +281,7 @@ wurde, bevor es ein bestimmtes Bedienelement gab, trotzdem irgendwo Sinnvollem l
|
||||
wird. Deine Mail bleibt genau so, wie sie geschrieben wurde — es wird nichts übersetzt und
|
||||
nichts umgeschrieben.
|
||||
|
||||
plMail liefert **English**, **Deutsch** und **Pirate English** mit. Eine Änderung lädt die
|
||||
plMail liefert **English**, **Deutsch**, **Français** und **Pirate English** mit. Eine Änderung lädt die
|
||||
Seite neu, statt sie nachzubessern, denn jede Zeichenkette auf dem Bildschirm muss neu
|
||||
gerendert werden.
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ sensible.
|
||||
**Settings → General → Language** sets the language the interface is shown in. Your mail is left
|
||||
exactly as it was written — nothing is translated, and nothing is rewritten.
|
||||
|
||||
plMail ships **English**, **Deutsch** and **Pirate English**. Changing it reloads the page rather
|
||||
plMail ships **English**, **Deutsch**, **Français** and **Pirate English**. Changing it reloads the page rather
|
||||
than patching it, because every string on screen has to be re-rendered.
|
||||
|
||||
The same section carries **Time zone**, which decides the clock times and dates you are shown. It
|
||||
|
||||
@@ -14,6 +14,7 @@ enum AppLocale: string
|
||||
{
|
||||
case English = 'en';
|
||||
case German = 'de';
|
||||
case French = 'fr';
|
||||
case Pirate = 'en_PI';
|
||||
|
||||
/**
|
||||
@@ -24,6 +25,7 @@ enum AppLocale: string
|
||||
return match ($this) {
|
||||
self::English => 'English',
|
||||
self::German => 'Deutsch',
|
||||
self::French => 'Français',
|
||||
self::Pirate => 'Pirate English',
|
||||
};
|
||||
}
|
||||
@@ -36,6 +38,7 @@ enum AppLocale: string
|
||||
return match ($this) {
|
||||
self::English => '🇬🇧',
|
||||
self::German => '🇩🇪',
|
||||
self::French => '🇫🇷',
|
||||
self::Pirate => '🏴☠️',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -87,7 +87,8 @@ enum ClockFormat: string
|
||||
public static function forLocale(?AppLocale $locale): self
|
||||
{
|
||||
return match ($locale) {
|
||||
AppLocale::German => self::TwentyFour,
|
||||
AppLocale::German,
|
||||
AppLocale::French => self::TwentyFour,
|
||||
default => self::Twelve,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -883,7 +883,7 @@ Variables: form, message, ctx (an App\Domain\DTO\Mail\ComposeContext)
|
||||
<div data-controller="ui--dropdown compose--emoji-picker"
|
||||
data-action="ui--dropdown:opened->compose--emoji-picker#build"
|
||||
data-compose--emoji-picker-locale-value="{{ app.request.locale|split('_')|first }}"
|
||||
data-compose--emoji-picker-data-source-value="{{ asset('emoji/' ~ (app.request.locale starts with 'de' ? 'de' : 'en') ~ '/data.json') }}"
|
||||
data-compose--emoji-picker-data-source-value="{{ asset('emoji/' ~ (app.request.locale|split('_')|first in ['de', 'fr'] ? app.request.locale|split('_')|first : 'en') ~ '/data.json') }}"
|
||||
data-compose--emoji-picker-i18n-value="{{ {
|
||||
categoriesLabel: 'compose.emoji.categories'|trans,
|
||||
emojiUnsupportedMessage: 'compose.emoji.unsupported'|trans,
|
||||
|
||||
@@ -39,7 +39,7 @@ final class EmbeddingPresetTest extends KernelTestCase
|
||||
$translator = self::getContainer()->get(TranslatorInterface::class);
|
||||
|
||||
foreach (EmbeddingPreset::ordered() as $preset) {
|
||||
foreach (['en', 'de', 'en_PI'] as $locale) {
|
||||
foreach (['en', 'de', 'fr', 'en_PI'] as $locale) {
|
||||
$key = $preset->summaryKey();
|
||||
$text = $translator->trans($key, [], 'messages', $locale);
|
||||
|
||||
|
||||
@@ -49,8 +49,12 @@ final class ClockFormatResolverTest extends TestCase
|
||||
$english = new User();
|
||||
$english->locale = 'en';
|
||||
|
||||
$french = new User();
|
||||
$french->locale = 'fr';
|
||||
|
||||
self::assertSame(ClockFormat::TwentyFour, $this->resolver()->resolve($german));
|
||||
self::assertSame(ClockFormat::Twelve, $this->resolver()->resolve($english));
|
||||
self::assertSame(ClockFormat::TwentyFour, $this->resolver()->resolve($french), 'French writes 14 h 00, never 2 PM');
|
||||
}
|
||||
|
||||
/** A user with no locale of their own follows the install's. */
|
||||
|
||||
Reference in new issue
Block a user