From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

#!/usr/bin/perl
#
# This file is part of App-SpreadRevolutionaryDate
#
# This software is Copyright (c) 2019-2025 by Gérald Sédrati.
#
# This is free software, licensed under:
#
# The GNU General Public License, Version 3, June 2007
#
use 5.014;
use utf8;
BEGIN {
$ENV{OUTPUT_CHARSET} = 'UTF-8';
}
# PODNAME: spread-revolutionary-date
# ABSTRACT: Spread date and time from Revolutionary (Republican) Calendar
App::SpreadRevolutionaryDate->new->spread;
__END__
=pod
=encoding UTF-8
=head1 NAME
spread-revolutionary-date - Spread date and time from Revolutionary (Republican) Calendar
=head1 VERSION
version 0.41
=head1 DESCRIPTION
C<spread-revolutionary-date> is a L<Free Software|https://www.gnu.org/philosophy/free-sw.html> that spreads the current date, expressed in the L<French Revolutionary calendar|https://en.wikipedia.org/wiki/French_Republican_calendar>, to various social networks: L<Bluesky|https://bsky.app/>, L<Twitter|https://twitter.com/>, L<Mastodon|https://mastodon.social/>, the L<Freenode|https://freenode.net/> and L<Liberachat|https://libera.chat/> Internet Relay Chat networks.
Moreover, you can easily extend these defaults targets with any desired one, see L</"EXTENDING TO NEW TARGETS">, and even spread something else than the revolutionary date, see L</msgmaker> option and L</"EXTENDING TO NEW MESSAGE MAKERS">.
The French Revolutionary calendar, also called Republican calendar, was introduced during the L<French Revolution|https://en.wikipedia.org/wiki/French_Revolution>, and used from late 1793 to 1805, and also during the L<Paris Commune|https://en.wikipedia.org/wiki/Paris_Commune> in 1871. This was an attempt to get rid of religious and royalist references found in Gregorian calendar when naming measures of Time. Months were given new names based on nature, each day of the year, instead of being named after an associated saint, had a unique name associated with the rural economy: agricultural tools, common animals, grains, pastures, trees, roots, flowers, fruits, plants, and minerals. But this was also an attempt to give more rational in measuring Time, basing measures on decimal system. Instead of weeks, each month was divided into exactly 3 I<décades>, that is ten days; days were divided into ten hours; hours into 100 minutes; and minutes into 100 seconds.
You B<must> have a registered account on each of the targets you want to spread the revolutionary date. And you must get credentials for C<spread-revolutionary-date> to post on Bluesky, Twitter and Mastodon. Finally, you have to configure C<spread-revolutionary-date> to use these credentials, see L</CONFIGURATION> and L</"COMMAND LINE PARAMETERS"> below.
The revolutionary date and time is computed thanks to the L<DateTime::Calendar::FrenchRevolutionary> Perl module, by Jean Forget.
=head1 USAGE
# Just execute the script in your shell
# to spread current date to configured accounts
# to Bluesky, Twitter, Mastodon, Freenode and Liberachat:
$ spread-revolutionary-date
# Or, since this script does nothing but calling
# the L<App::SpreadRevolutionaryDate> Perl module,
# use this one-liner:
$ perl -MApp::SpreadRevolutionaryDate \
-e 'App::SpreadRevolutionaryDate->new->spread;'
# Test spreading to Mastodon only:
$ spread-revolutionary-date \
--targets=mastodon --test
# Test spreading to Twitter only in English:
$ spread-revolutionary-date \
--targets=twitter \
--test \
--locale en
# Spread acab time to Twitter and Liberachat
# explicit channels
$ spread-revolutionary-date \
--targets=twitter \
--targets=liberachat \
--liberachat_channels='#revolution' \
--liberachat_channels='#acab' \
--revolutionarydate_acab
# Prompt user for a message to spread to mastodon
$ spread-revolutionary-date \
--targets=mastodon \
--msgmaker=UserPrompt
# Spread message as command line parameter to
# Bluesky, Twitter, Mastodon, Freenode and Liberachat
$ spread-revolutionary-date \
--msgmaker=UserPrompt \
--promptuser_default \
# Spread message and image as command line parameter to
# Mastodon and Bluesky
$ spread-revolutionary-date \
--msgmaker=UserPrompt \
--targets=mastodon \
--targets=bluesky \
--promptuser_default \
'This is my message to the world'
--promptuser_img_path= \
/my/path/to/image.png
--promptuser_img_alt= \
'Alternative text for image'
# Spread message and image form web as command line parameter to
# Mastodon and Bluesky
$ spread-revolutionary-date \
--msgmaker=UserPrompt \
--targets=mastodon \
--targets=bluesky \
--promptuser_default \
'This is my message to the world'
--promptuser_img_url= \
--promptuser_img_alt \
# Spread Téléchat date of the day on Mastodon and BlueSky
$ spread-revolutionary-date \
--msgmaker=Telechat \
--targets=mastodon \
--targets=bluesky \
=head1 CONFIGURATION
Once again: you B<have to> configure C<spread-revolutionary-date> with credentials for registered account on each of the desired targets, so it can spread the revolutionary date on behalf of these accounts.
Configuration options may also be specified as command line parameters, see L</"COMMAND LINE PARAMETERS"> below, which take precedence on options of the configuration file.
The configuration file should lie on C<~/.config/spread-revolutionary-date/spread-revolutionary-date.conf> or C<~/.spread-revolutionary-date.conf>. In case a file is found on both paths, the second one is ignored. The configuration file should use the popular L<INI file format|https://en.wikipedia.org/wiki/INI_file>. A sample configuration file can be found in this distribution at C<etc/sample-spread-revolutionary-date.conf>.
=head2 General options
These options should appear outside of any section of the configuration file.
=head3 targets
This option can be specified multiple times, with values as strings. It explicitly defines targets where the revolutionary date should be spread to. Any value set for this option should be a valid target: any of the five default targets (C<Bluesky>, C<twitter>, C<mastodon>, C<freenode>, or C<liberachat>) or some new target if you have extended this application (see L</"EXTENDING TO NEW TARGETS">). If this option is not defined, the revolutionary date is spread on all five default targets: C<Bluesky>, C<twitter>, C<mastodon>, C<freenode> and C<liberachat>.
=head3 msgmaker
This option can only be specified once, with a value as string. Spreads a message computed by the class defined by the value of this option, defaults to C<RevolutionaryDate>. The C<Value> (case sensitive) of this option should correspond to an existing C<App::SpreadRevolutionaryDate::MsgMaker::Value> class consuming L<App::SpreadRevolutionaryDate::MsgMaker> role. Message makers values pre-defined in this distribution are C<RevolutionaryDate>, which spreads the revolutionary date, C<PromptUser>, which prompts the user for the message to be spread (with confirmation), and C<Telechat>, which spreads the date of the day similar to the Belgian-French TV show 'Téléchat" on the 1980's. See L</"EXTENDING TO NEW MESSAGE MAKERS"> for details on using a new value for this option.
=head3 locale
This option can only be specified once, with a value as string. Spreads with chosen language. As of L<App::SpreadRevolutionaryDate> 0.11 locale is limited to C<'fr'>, C<'en'>, C<'it'> or C<'es'> for C<RevolutionaryDate> and C<'fr'>, C<'en'>, C<'it'>, C<'es'> or C<'de'> otherwise. Defaults to C<'fr'> for C<RevolutionaryDate> and C<'en'> otherwise. To add more languages see L</INTERNATIONALIZATION AND LOCALIZATION>.
=head3 test
This boolean option takes no value, either it is defined or not. If defined, do not actually spread the revolutionary date, just print it on standard output for Bluesky, Twitter and Mastodon, and send it on configured test channels for Freenode and Liberachat (see L</"test_channels"> below).
=head2 Bluesky options
These options are credentials for C<spread-revolutionary-date> to spread on a Bluesky account. You have to get them from your L<Bluesky account|https://bsky.app/>. They should be defined in the C<[bluesky]> section of the configuration file.
=head3 identifier
This option can only be specified once, with a value as string: your Bluesky identifier (ending with C<.bsky.social> by default).
=head3 password
This option can only be specified once, with a value as string: your Bluesky password. You can define a special password for this application, in order to not use your main Bluesky password in your L<Bluesky account settings|https://bsky.app/settings/app-passwords>.
=head2 Twitter options
These options are credentials for C<spread-revolutionary-date> to spread on a Twitter account. You have to get them from your L<Twitter API account|https://apps.twitter.com/> with C<write> access level. They should be defined in the C<[twitter]> section of the configuration file.
=head3 consumer_key
This option can only be specified once, with a value as string: your Twitter Consumer API key for this application.
=head3 consumer_secret
This option can only be specified once, with a value as string: your Twitter Consumer API secret key for this application.
=head3 access_token
This option can only be specified once, with a value as string: your Twitter Access token for this application.
=head3 access_token_secret
This option can only be specified once, with a value as string: your Twitter Access token secret for this application.
=head2 Mastodon options
These options are credentials for C<spread-revolutionary-date> to spread on a Mastodon account. You have to get them from your L<Mastodon instance API account|https://mstdn.fr/settings/applications> with C<write> scope. Note that Mastodon is a decentralized network with multiple instances, the previous link is for L<mstdn.fr|https://mstdn.fr> instance, please replace url with your preferred instance. They should be defined in the C<[mastodon]> section of the configuration file.
=head3 instance
This option can only be specified once, with a value as string: the domain name of your instance, eg: C<mastodon.social>, C<mstdn.fr>, etc.
=head3 client_id
This option can only be specified once, with a value as string: your Mastodon Client key for this application.
=head3 client_secret
This option can only be specified once, with a value as string: your Mastodon Client secret for this application.
=head3 access_token
This option can only be specified once, with a value as string: your Mastodon Access token for this application.
=head2 Freenode options
The first two options are credentials for C<spread-revolutionary-date> to spread on a Freenode account. See L<https://freenode.net/kb/answer/registration> to find out how to register an account on Freenode. They should be defined in the C<[freenode]> section of the configuration file.
=head3 nickname
This option can only be specified once, with a value as string: your Freenode nickname.
=head3 password
This option can only be specified once, with a value as string: your Freenode password.
=head3 channels
This option can be specified multiple times, with values as strings. C<spread-revolutionary-date> will spread on every channel specified with this option. This option should be specified at least one time if L</test> option is not set. It is ignored if L</test> option is set.
=head3 test_channels
This option can be specified multiple times, with values as strings. C<spread-revolutionary-date> will spread on every channel specified with this option. This option should be specified at least one time if L</test> option is set. It is ignored if L</test> option is not set.
=head2 Liberachat options
The first two options are credentials for C<spread-revolutionary-date> to spread on a Liberachat account. See L<https://libera.chat/guides/registration> to find out how to register an account on Liberachat. They should be defined in the C<[liberachat]> section of the configuration file.
=head3 nickname
This option can only be specified once, with a value as string: your Liberachat nickname.
=head3 password
This option can only be specified once, with a value as string: your Liberachat password.
=head3 channels
This option can be specified multiple times, with values as strings. C<spread-revolutionary-date> will spread on every channel specified with this option. This option should be specified at least one time if L</test> option is not set. It is ignored if L</test> option is set.
=head3 test_channels
This option can be specified multiple times, with values as strings. C<spread-revolutionary-date> will spread on every channel specified with this option. This option should be specified at least one time if L</test> option is set. It is ignored if L</test> option is not set.
=head2 RevolutionaryDate options
These options change the way revolutionary date is computed when L</msgmaker> option is C<RevolutionaryDate>. They should be defined in the C<[revolutionarydate]> section of the configuration file.
=head3 acab
This boolean option takes no value, either it is defined or not, defaults to C<false>. If defined, instead of spreading the current date and time, pretend that decimal time is 1:31:20 (which corresponds to 03:08:56 UTC, 04:08:56 Paris winter time, or 05:08:56 Paris summer time, in sexagesimal scale used by common Anglo-Babylonian Time).
=head3 wikipedia_link
This boolean option takes no value, either it is defined or not, defaults to C<true>. If defined, a link to the wikipedia page, in language defined by the L<locale> option, corresponding to the feast of the day, is added to the date to be spread.
=head2 PromptUser option
If L</msgmaker> option is C<PromptUser>, instead of spreading the revolutionary date, prompts the user (with confirmation) for the message to be spread, with a default value (if user enters nothing when prompted).
=head3 default
This option can only be specified once, with a value as string. If C<default> option is set, the user is not prompted and this default message is spread. If C<default> option is not defined, the default message is C<'Goodbye old world, hello revolutionary worlds'> if the user enters nothing when prompted. The C<default> option should be defined in the C<[promptuser]> section of the configuration file. It is only used if L</msgmaker> option is C<PromptUser>.
=head3 img_path
This option can only be specified once, with a value as string valued by a path to an image file on local disk. The C<img_path> option should be defined in the C<[promptuser]> section of the configuration file. It is only used if L</msgmaker> option is C<PromptUser>.
=head3 img_alt
This option can only be specified once, with a value as string valued by an alternative text to an image file specified by C<img_path> or C<img_url> options. The C<img_alt> option should be defined in the C<[promptuser]> section of the configuration file. It is only used if L</msgmaker> option is C<PromptUser>.
=head3 img_url
This option can only be specified once, with a value as string valued by an external url to an image specified. The C<img_url> option should be defined in the C<[promptuser]> section of the configuration file. It is only used if L</msgmaker> option is C<PromptUser>.
=head1 COMMAND LINE PARAMETERS
Any command line parameter, other than the first three ones below, takes precedence on the corresponding option specified on the confiuration file, see L</CONFIGURATION> above.
=head2 Command line only parameters
=head3 --conf=<file> | -c <file>
Use C<E<lt>fileE<gt>> path as configuration file, instead of the default ones, see L</CONFIGURATION> above.
=head3 --version | -v
Prints out the version of C<spread-revolutionary-date>.
=head3 --help | -h | -?
Prints out help with command line parameters.
=head2 General parameters
=head3 --targets=<target> | -tg <target>
Same as L</targets> configuration option above.
=head3 --msgmaker=<MsgMakerClass> | -mm <MsgMakerClass>
Same as L</msgmaker> configuration option above.
=head3 --locale=<fr|en|it|es> | -l <fr|en|it|es>
Same as L</locale> configuration option above.
=head3 --test | --no | -n
Same as L</test> configuration option above.
=head2 Bluesky parameters
These parameters specify credentials for C<spread-revolutionary-date> to spread on a Bluesky account. You have to get them from your L<Bluesky account|https://bsky.app/>.
=head3 --bluesky_identifier=<identifier> | -bi <identifier>
Same as L</identifier> configuration option above.
=head3 --bluesky_password=<password> | -bp <password>
Same as L</password> configuration option above.
=head2 Twitter parameters
These parameters specify credentials for C<spread-revolutionary-date> to spread on a Twitter account. You have to get them from your L<Twitter API account|https://apps.twitter.com/> with C<write> access level.
=head3 --twitter_consumer_key=<key> | -tck <key>
Same as L</consumer_key> configuration option above.
=head3 --twitter_consumer_secret=<secret> | -tcs <secret>
Same as L</consumer_secret> configuration option above.
=head3 --twitter_access_token=<token> | -tat <token>
Same as L</access_token> configuration option above.
=head3 --twitter_access_token_secret=<token secret> | -tats <token secret>
Same as L</access_token_secret> configuration option above.
=head2 Mastodon parameters
These parameters specify credentials for C<spread-revolutionary-date> to spread on a Mastodon account. You have to get them from your L<Mastodon instance API account|https://mstdn.fr/settings/applications> with C<write> scope. Note that Mastodon is a decentralized network with multiple instances, the previous link is for L<mstdn.fr|https://mstdn.fr> instance, please replace url with your preferred instance.
=head3 --mastodon_instance=<instance> | -mi <instance>
Same as L</instance> configuration option above.
=head3 --mastodon_client_id=<id> | -mci <id>
Same as L</client_id> configuration option above.
=head3 --mastodon_client_secret)<secret> | -mcs <secret>
Same as L</client_secret> configuration option above.
=head3 --mastodon_access_token=<token> | -mat <token>
Same as L</access_token> configuration option above.
=head2 Freenode parameters
The first two parameters are credentials for C<spread-revolutionary-date> to spread on a Freenode account. See L<https://freenode.net/kb/answer/registration> to find out how to register an account on Freenode.
=head3 --freenode_nickname=<nick> | -fn <nick>
Same as L</nickname> configuration option above.
=head3 --freenode_password=<passwd> | -fp <passwd>
Same as L</password> configuration option above.
=head3 --freenode_channels=<channel> | -fc <channel>
Same as L</channels> configuration option above.
=head3 --freenode_test_channels=<channel> | -ftc <channel>
Same as L</test_channels> configuration option above.
=head2 Liberachat parameters
The first two parameters are credentials for C<spread-revolutionary-date> to spread on a Liberachat account. See L<https://libera.chat/guides/registration> to find out how to register an account on Liberachat.
=head3 --liberachat_nickname=<nick> | -ln <nick>
Same as L</nickname> configuration option above.
=head3 --liberachat_password=<passwd> | -lp <passwd>
Same as L</password> configuration option above.
=head3 --liberachat_channels=<channel> | -lc <channel>
Same as L</channels> configuration option above.
=head3 --liberachat_test_channels=<channel> | -ltc <channel>
Same as L</test_channels> configuration option above.
=head2 RevolutionaryDate parameters
=head3 --revolutionarydate_acab | -ra
Same as L</acab> configuration option above.
=head3 --revolutionarydate_wikipedia_link
Same as L</wikipedia_link> configuration option above.
=head2 PromptUser parameter
=head3 --promptuser_default <msg> | -pud <msg>
Same as L</default> configuration option above.
=head3 --promptuser_img_path <path/to/image/file> | -pui </path/to/image/file>
Same as L</img_path> configuration option above.
=head3 --promptuser_img_alt <alternative text> | -pud <alternative text>
Same as L</img_alt> configuration option above.
=head3 --promptuser_default <img_url> | -pud <img_url>
Same as L</img_url> configuration option above.
=head1 EXTENDING TO NEW TARGETS
Starting from version 0.07, this distribution takes advantage of L<Moose>, the postmodern object system for Perl 5, allowing to easily extend C<spread-revolutionary-date> to other targets than the default ones (C<Bluesky>, C<Twitter>, C<Mastondon>, C<Freenode> and C<Liberachat>.
To add a new target, you should write a new class in the C<App::SpreadRevolutionaryDate::Target::> namespace (that is: the class should be C<App::SpreadRevolutionaryDate::Target::Mytarget> for a new C<Mytarget> target), that consumes the L<App::SpreadRevolutionaryDate::Target> role. See L<App::SpreadRevolutionaryDate::Target/DESCRIPTION> for a comprehensive description of this role.
The name of the target should be added as a value of the L</targets> option in lower case.
Such a target class is actually just a wrapper. Usually a target has to use an existing specific module (which can be a C<Moose> class or not) to perform the actual work of posting a message according the specific target protocol, after having complied with any potential required authentication. Such authentication should be carried on by the constructor or, along with posting, by the required C<spread> method of the target class.
To perform authentication and to post a message, there is a strong likelihood that the new target requires specific parameters (e.g.: tokens, keys, account name, password, channels, etc.). These parameters should be defined as required attributes of the target class. Values for such attributes should be set in the L<configuration file|/CONFIGURATION>, inside a section named after the target in lower case (C<[mytarget]>), or as L<command line parameters|/"COMMAND LINE PARAMETERS"> prefixed with the name of the target in lower case, followed by an underscore (C<--mytarget_myparam>).
Should you extend C<spread-revolutionary-date> to a new target, we advise you to have a look on how default targets are implemented: L<App::SpreadRevolutionaryDate::Target::Bluesky> with L<App::SpreadRevolutionaryDate::BlueskyLite> C<worker>, L<App::SpreadRevolutionaryDate::Target::Twitter> with L<Twitter::API> C<worker>, L<App::SpreadRevolutionaryDate::Target::Mastodon> with L<Mastodon::Client> C<worker>. The last two are using L<OAuth2 protocol|https://oauth.net/2/> to perform authentication. The third and fourth default targets, C<App::SpreadRevolutionaryDate::Target::Freenode> <App::SpreadRevolutionaryDate::Target::Liberachat>, uses a L<chatbot|https://en.wikipedia.org/wiki/Chatbot>: L<App::SpreadRevolutionaryDate::Target::Freenode::Bot> L<App::SpreadRevolutionaryDate::Target::Liberachat::Bot> subclassing L<Bot::BasicBot>. You can also see a very simple example with a test file provided in this distribution at C<t/new_target.t>, which just prints out the revolutionary date on the standard output using core module L<IO::Handle>.
Starting from version 0.39, you may have noticed that C<Mastodon> and C<Bluesky> targets can no spread not only a text message, but also an image (with alternative text). This is used by C<Telechat> message maker, to post an image of Groucha, the presenter of Téléchat, and by C<PromptUser> to send either an image file on local disk or an external image on the web.
This feature is not available now for I<IRC> targets, C<Liberachat> and C<Freenode>, since theses targets are mostly for text messages.
Also, we do not plan to extend this feature to C<Twitter> target, since we recommand to not use this social network for political reasons.
=head1 EXTENDING TO NEW MESSAGE MAKERS
It is even easier to spread whatever you want instead of the revolutionary date. You should write a new class in the C<App::SpreadRevolutionaryDate::MsgMaker::> namespace (that is: the class should be C<App::SpreadRevolutionaryDate::MsgMaker::MyMsgMaker> for a new C<MyMsgMaker> message maker), that consumes the L<App::SpreadRevolutionaryDate::MsgMaker> role. See L<App::SpreadRevolutionaryDate::MsgMaker/DESCRIPTION> for a comprehensive description of this role.
The name of the message maker should be set as a value of the L</msgmaker> option in lower case.
Such a message maker class is actually just a wrapper. Usually a message maker has to use an existing specific module (which can be a C<Moose> class or not) to craft the message. L<App::SpreadRevolutionaryDate::MsgMaker::RevolutionaryDate> uses L<DateTime::Calendar::FrenchRevolutionary>, while L<App::SpreadRevolutionaryDate::MsgMaker::PromptUser> is based on L<IO::Prompt::Hooked>. You may need for example L<LWP> to extract the message from a fetched web page or service, or L<XML::Feed> to build it from a L<RSS|https://en.wikipedia.org/wiki/RSS> feed, or L<DBI> to retrieve it from a database, or nothing at all to spread a fixed message, etc.
If your new message maker class needs specific parameters (other than C<locale>, which comes with L<App::SpreadRevolutionaryDate::MsgMaker> role), they should be defined as attributes of this class. Values for such attributes should be set in the L<configuration file|/CONFIGURATION>, inside a section named after the message maker in lower case (C<[mymsgmaker]>), or as L<command line parameters|/"COMMAND LINE PARAMETERS"> prefixed with the name of the message maker in lower case, followed by an underscore (C<--mytarget_myparam>).
Have a look to L<App::SpreadRevolutionaryDate::MsgMaker::PromptUser> or L<App::SpreadRevolutionaryDate::MsgMaker::Telechat> classes, it shows simple examples on how to extend C<spread-revolutionary-date> to a new message maker.
=head1 INTERNATIONALIZATION AND LOCALIZATION
Starting from version 0.11, this distribution uses the widespread internationalization and localization system L<gettext|https://en.wikipedia.org/wiki/Gettext>, commonly used for writing multilingual programs. See L<GNU gettext documentation|https://www.gnu.org/software/gettext/gettext.html> for details. From the point of view of a translator, this is rather simple.
Translators can find a portable object template C<po/App-SpreadRevolutionaryDate.pot> which includes all translatable strings used by C<spread-revolutionary-date> (but not translations of days, months, feasts used in the French Revolutionary Calendar, see below). They can copy this template to a portable object file of their language and translate strings of this file. For example, a German translator would work on C<po/de.po>. All strings to be translated are laid down in lines beginning with C<msgid> keyword, and translations should go on the next line beginning with keyword C<msgstr>. E.g.:
msgid "Please, enter message to spread"
A German translator would have to replace the next line:
msgstr ""
by:
msgstr "Bitte geben Sie die Nachricht zu verbreiten ein"
When the string to be translated includes some words in curly braces, these words are actually named variables and should be left as is in the translation. E.g.:
msgid "or {abort} to abort"
msgstr "oder {abort}, um abzubrechen"
And that's it! As of version 0.11 of C<spread-revolutionary-date>, there is only about a dozen of strings to translate, mainly for C<PromptUser> message maker. But with the possibility to extend to other message makers, you may need more and more strings to be translated.
Translating days, months and feasts used in the C<RevolutionaryDate> message maker do not use the C<gettext> system. Mainly because it uses L<DateTime::Calendar::FrenchRevolutionary> which proposes French and English translations in dedicated Perl modules. C<spread-revolutionary-date> keeps the same way for translating expressions used in the French revolutionary calendar, but, thanks to L<Moose::Role> L<App::SpreadRevolutionaryDate::MsgMaker::RevolutionaryDate::Locale>, translatable nominal groups have been isolated from other Perl code. Their translations lie in a consuming class in the C<App::SpreadRevolutionaryDate::MsgMaker::RevolutionaryDate::Locale::> namespace for each translated language. These classes are named after the two-letter (L<ISO 639-1|https://en.wikipedia.org/wiki/ISO_639-1>) or three-letter (L<ISO 639-2|https://en.wikipedia.org/wiki/ISO_639-2> and L<ISO 639-3|https://en.wikipedia.org/wiki/ISO_639-3>) lowercase abbreviation of the corresponding language. For example, a German translator would work on C<App::SpreadRevolutionaryDate::MsgMaker::RevolutionaryDate::Locale::de> class.
Just copy the French class (from C<lib/App/SpreadRevolutionaryDate/MsgMaker/RevolutionaryDate/Locale/fr.pm> file) into the desired language, change the name of the class and replace every French string. E.g.: the names of the months should be replace in:
has '+months' => (
default => sub {[
'Vendémiaire', 'Brumaire', 'Frimaire',
'Nivôse', 'Pluviôse', 'Ventôse',
'Germinal', 'Floréal', 'Prairial',
'Messidor', 'Thermidor', 'Fructidor',
'jour complémentaire',
]},
);
by names in German:
has '+months' => (
default => sub {[
'Herbstmonat', 'Nebelmonat', 'Reifmonat',
'Schneemonat', 'Regenmonat', 'Windmonat',
'Keimmonat', 'Blütenmonat', 'Wiesenmonat',
'Erntemonat', 'Hitzemonat', 'Fruchtmonat',
'Ergänzungstage',
]},
);
Feasts include a special trick, because they can be used in sentences like I<this is C<feast name> day> or I<c'est le jour de la C<feast name>>. Depending on the language, it could then be prefixed or suffixed: in English it is suffixed by C< day>, whereas in French it is prefixed by C<jour de la >. Prefixes are translated as an array of strings, while the suffix is translated in a single string. The reason is that in languages where the feast of the day is prefixed, the prefix often depends on the gender or the number of the noun used for the feast, or whereas this noun starts by a vowel, and other factors depending on the language. Therefore, each translation of the feast of each day should starts with a digit specifying the index (starting from 0) in the translated array of prefixes to use for this word. E.g.: with prefixes translated by C<['jour du ', 'jour de la ', "jour de l'", 'jour des ']>, some feast can be translated by C<'1carotte', '2amaranthe', '0panais'> (because you say: I<jour de la carotte>, with prefix number C<1>, I<jour de l'amaranthe>, with prefix number C<2>, and I<jour du panais>, with prefix number C<0>). If the language does not use any prefix before the feast of the day, each translation for the feast of the day should start with C<0>, and the array of prefixes should include an empty string as its single element. If the language does not use a suffix after the feast of the day, the translation of the sufix should be an empty string.
Note also that any space in the name of the feast of the day should be replaced by an underscore (C<_>).
Finally, these translation classes include a mapping between the feast of the day and the wikipedia entry for this word. This is useful when the feast of the day corresponds to an ambiguous entry, or a different word, in wikipedia. If the wikipedia entry is the same as the feast of the day, you can omit it. If the wikipedia entry is different from the feast of the day, you should add a line in the appropriate group of mappings for the considered month (groups of mappings are numbered from 1 to 13). In the left part of this new mapping you should use the feast of the day as you have translated it, but without the number indicating the prefix and with spaces, not underscores. And in the right part of this new mapping, you should use the wikipedia entry, i.e. the end of the wikipedia url. E.g.:
has '+wikipedia_entries' => (
default => sub {{
2 => {
'water chestnut' => 'Water_caltrop',
},
8 => {
'hoe' => 'Hoe_(tool)',
},
}},
);
Because of the trick on prefix and suffix for feasts and the needed mapping for wikipedia entries, using the C<gettext> system would be quite difficult. It wouldn't be an issue for translating names of months or days. But for consistency reasons, I'd rather group all these translations used in the French Revolutionary Calendar in the same translation class. Nevertheless, I'm open to find solutions if you think it would be easier to translate everything with the C<gettext> system.
=head1 SEE ALSO
=over
=item L<App::SpreadRevolutionaryDate>
=item L<DateTime::Calendar::FrenchRevolutionary>
=item L<AppConfig>
=item L<App::SpreadRevolutionaryDate::BlueskyLite>
=item L<Twitter::API>
=item L<Mastodon::Client>
=item L<Bot::BasicBot>
=back
=head1 AUTHOR
Gérald Sédrati <gibus@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2019-2025 by Gérald Sédrati.
This is free software, licensed under:
The GNU General Public License, Version 3, June 2007
=cut