diff options
| author | Greg Roach <greg@subaqua.co.uk> | 2021-06-22 15:34:14 +0100 |
|---|---|---|
| committer | Greg Roach <greg@subaqua.co.uk> | 2021-06-22 15:34:14 +0100 |
| commit | 30e63383b10bafff54347985dcdbd10c40c33f62 (patch) | |
| tree | 71a3a208735947727ff41a0cb4ddc4a2bddae613 /modules_v4 | |
| parent | ea024074a4a6fddf1e62fd2b95467c6e41c945e4 (diff) | |
| download | webtrees-30e63383b10bafff54347985dcdbd10c40c33f62.tar.gz webtrees-30e63383b10bafff54347985dcdbd10c40c33f62.tar.bz2 webtrees-30e63383b10bafff54347985dcdbd10c40c33f62.zip | |
Fix: #3895 - move example modules to the webtrees repository
Diffstat (limited to 'modules_v4')
13 files changed, 5 insertions, 854 deletions
diff --git a/modules_v4/README.md b/modules_v4/README.md index a41d758268..131e3bea80 100644 --- a/modules_v4/README.md +++ b/modules_v4/README.md @@ -9,9 +9,9 @@ A module is a folder containing a file called `module.php`. There may be other files in the folder, such as CSS, JS, templates, languages, data, etc. -To install a module, copy its folder to `modules_v4`. +To install a module, copy its folder to `/modules_v4`. -To uninstall it, delete its folder from `modules_v4`. +To uninstall it, delete its folder from `/modules_v4`. Note that module names (i.e. the folder names) must not contain spaces or the characters `.`, `[` and `]`. It must also have a @@ -25,198 +25,8 @@ modules containing `.` are ignored. To write a module, you need to understand the PHP programming language. -The rest of this document is aimed at PHP developers. +There are several example modules available at +https://github.com/webtrees -TIP: The built-in modules can be found in `app/Module/*.php`. +The built-in modules can be found in `app/Module/`. These contain lots of useful examples that you can copy/paste. - -## Creating a custom module. - -This is the minimum code needed to create a custom module. - -```php -<?php - -use Fisharebest\Webtrees\Module\AbstractModule; -use Fisharebest\Webtrees\Module\ModuleCustomInterface; -use Fisharebest\Webtrees\Module\ModuleCustomTrait; - -return new class extends AbstractModule implements ModuleCustomInterface { - use ModuleCustomTrait; - - /** - * How should this module be labelled on tabs, menus, etc.? - * - * @return string - */ - public function title(): string - { - return 'My Custom module'; - } - - /** - * A sentence describing what this module does. - * - * @return string - */ - public function description(): string - { - return 'This module doesn‘t do anything'; - } -}; -``` - -If you plan to share your modules with other webtrees users, you should -provide them with support/contact/version information. This way they will -know where to go for updates, support, etc. -Look at the functions and comments in `app/ModuleCustomTrait.php`. - -## Available interfaces - -Custom modules *must* implement `ModuleCustomInterface` interface. -They *may* implement one or more of the following interfaces: - -* `ModuleAnalyticsInterface` - adds a tracking/analytics provider. -* `ModuleBlockInterface` - adds a block to the home pages. -* `ModuleChartInterface` - adds a chart to the chart menu. -* `ModuleDataFixInterface` - adds a data fix to the manage-trees page. -* `ModuleConfigInterface` - adds a configuration page to the control panel. -* `ModuleGlobalInterface` - adds CSS and JS to all page. -* `ModuleListInterface` - adds a list to the list menu. -* `ModuleMenuInterface` - adds an entry to the main menu. -* `ModuleReportInterface` - adds a report to the report menu. -* `ModuleSidebarInterface` - adds a sidebar to the individual pages. -* `ModuleTabInterface` - adds a tab to the individual pages. -* `ModuleThemeInterface` - adds a theme (this interface is still being developed). - -For each module interface that you implement, you must also use the corresponding trait. -If you don't do this, your module may break whenever the module interface is updated. - -Where possible, the interfaces won't change - however new methods may be added -and existing methods may be deprecated. - -Modules may also implement the following interfaces, which allow them to integrate -more deeply into the application. - -* `MiddlewareInterface` - allows a module to intercept the HTTP request/response cycle. - -## How to extend/modify an existing modules - -To create a module that is just a modified version of an existing module, -you can extend the existing module (instead of extending `AbstractModule`). - -```php -<?php -use Fisharebest\Webtrees\Module\ModuleCustomInterface; -use Fisharebest\Webtrees\Module\ModuleCustomTrait; -use Fisharebest\Webtrees\Module\PedigreeChartModule; -use Fisharebest\Webtrees\Services\ChartService; - -/** - * Creating an anonymous class will prevent conflicts with other custom modules. - */ -return new class extends PedigreeChartModule implements ModuleCustomInterface { - use ModuleCustomTrait; - - /** - * The chart needs some chart functions. We could pass in our own version here. - */ - public function __construct() - { - parent::__construct(new ChartService()); - } - - /** - * @return string - */ - public function description(): string - { - return 'A modified version of the pedigree chart'; - } - - // Change the default layout... - protected const DEFAULT_STYLE = self::STYLE_DOWN; - - protected const DEFAULT_PARAMETERS = [ - 'generations' => self::DEFAULT_GENERATIONS, - 'style' => self::DEFAULT_STYLE, - ]; -}; -``` - -## Dependency Injection - -webtrees uses the “Dependency Injection” pattern extensively. This is a system for -automatically generating objects. The advantages over using `new SomeClass()` are - -* Easier testing - you can pass "dummy" objects to your class. -* Run-time resolution - you can request an Interface, and webtrees will find a specific instance for you. -* Can swap implementations at runtime. - -Note that you cannot type-hint the following objects in the constructor, as they are not -created until after the modules. - -* other modules -* interfaces, such as `UserInterface` or `LocaleInterface` (the current user and language) -* the current tree `Tree` or objects that depend on it (`Statistics`) -as these objects are not created until after the module is created. - -Instead, these objects can be obtained from the request object. e.g. -```php -$tree = $request->getAttribute('tree'); -$user = $request->getAttribute('user'); -``` - -```php -<?php -use Fisharebest\Webtrees\Module\AbstractModule; -use Fisharebest\Webtrees\Module\ModuleCustomInterface; -use Fisharebest\Webtrees\Module\ModuleCustomTrait; -use Fisharebest\Webtrees\Services\TimeoutService; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -/** - * Creating an anoymous class will prevent conflicts with other custom modules. - */ -return new class extends AbstractModule implements ModuleCustomInterface { - use ModuleCustomTrait; - - /** @var TimeoutService */ - protected $timeout_service; - - /** - * IMPORTANT - the constructor is called for *all* modules, even ones - * that are disabled. You should do little more than initialise your - * private/protected members. - * - * @param TimeoutService $timeout_service - */ - public function __construct(TimeoutService $timeout_service) - { - $this->timeout_service = $timeout_service; - } - - /** - * Methods that are called in response to HTTP requests use - * dependency-injection. You'll almost certainly need the request - * object. - * - * @param ServerRequestInterface $request - * - * @return ResponseInterface - */ - public function getFooBarAction(ServerRequestInterface $request): ResponseInterface - { - // This assumes that there is a tree parameter in the URL. - $tree = $request->getAttribute('tree'); - - // This will be a User (or a GuestUser for visitors). - $user = $request->getAttribute('user'); - - $html = $tree->name() . '/' . $user->realName(); - - return response($html); - } -}; -``` diff --git a/modules_v4/example-footer.disable/module.php b/modules_v4/example-footer.disable/module.php deleted file mode 100644 index fc2b35a9ee..0000000000 --- a/modules_v4/example-footer.disable/module.php +++ /dev/null @@ -1,85 +0,0 @@ -<?php - -/** - * Example footer with a link to a page of information. - */ - -declare(strict_types=1); - -namespace MyCustomNamespace; - -use Fisharebest\Webtrees\Module\AbstractModule; -use Fisharebest\Webtrees\Module\ModuleCustomInterface; -use Fisharebest\Webtrees\Module\ModuleCustomTrait; -use Fisharebest\Webtrees\Module\ModuleFooterInterface; -use Fisharebest\Webtrees\Module\ModuleFooterTrait; -use Fisharebest\Webtrees\View; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -return new class extends AbstractModule implements ModuleCustomInterface, ModuleFooterInterface { - use ModuleCustomTrait; - use ModuleFooterTrait; - - /** - * @return string - */ - public function title(): string - { - return 'Custom footer'; - } - - /** - * Bootstrap the module - */ - public function boot(): void - { - // Register a namespace for our views. - View::registerNamespace($this->name(), $this->resourcesFolder() . 'views/'); - } - - /** - * Where does this module store its resources - * - * @return string - */ - public function resourcesFolder(): string - { - return __DIR__ . '/resources/'; - } - - /** - * A footer, to be added at the bottom of every page. - * - * @param ServerRequestInterface $request - * - * @return string - */ - public function getFooter(ServerRequestInterface $request): string - { - $tree = $request->getAttribute('tree'); - - $url = route('module', [ - 'module' => $this->name(), - 'action' => 'Page', - 'tree' => $tree ? $tree->name() : null, - ]); - - return view($this->name() . '::footer', ['url' => $url]); - } - - /** - * Generate the page that will be shown when we click the link in the footer. - * - * @param ServerRequestInterface $request - * - * @return ResponseInterface - */ - public function getPageAction(ServerRequestInterface $request): ResponseInterface - { - return $this->viewResponse($this->name() . '::page', [ - 'title' => $this->title(), - 'tree' => $request->getAttribute('tree'), - ]); - } -}; diff --git a/modules_v4/example-footer.disable/resources/views/footer.phtml b/modules_v4/example-footer.disable/resources/views/footer.phtml deleted file mode 100644 index f553598505..0000000000 --- a/modules_v4/example-footer.disable/resources/views/footer.phtml +++ /dev/null @@ -1,4 +0,0 @@ -<div class="wt-footer wt-footer-custom text-center py-2"> - Click <a href="<?= e($url) ?>">here</a> for more information. -</div> - diff --git a/modules_v4/example-footer.disable/resources/views/page.phtml b/modules_v4/example-footer.disable/resources/views/page.phtml deleted file mode 100644 index 872336957f..0000000000 --- a/modules_v4/example-footer.disable/resources/views/page.phtml +++ /dev/null @@ -1,3 +0,0 @@ -<h2><?= e($title) ?></h2> - -<p>Be nice to fish!</p> diff --git a/modules_v4/example-middleware.disable/module.php b/modules_v4/example-middleware.disable/module.php deleted file mode 100644 index 8ebeff23d4..0000000000 --- a/modules_v4/example-middleware.disable/module.php +++ /dev/null @@ -1,112 +0,0 @@ -<?php - -/** - * An example module to demonstrate middleware. - */ - -declare(strict_types=1); - -namespace MyCustomNamespace; - -use Fisharebest\Webtrees\Module\AbstractModule; -use Fisharebest\Webtrees\Module\ModuleCustomInterface; -use Fisharebest\Webtrees\Module\ModuleCustomTrait; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; -use Psr\Http\Server\RequestHandlerInterface; - -use function preg_match; -use function response; - -return new class extends AbstractModule implements ModuleCustomInterface, MiddlewareInterface { - use ModuleCustomTrait; - - // Regular-expressions to match unwanted bots. - private const BAD_USER_AGENTS = [ - '/AhrefsBot/', - '/MJ12bot/', - '/SeznamBot/', - ]; - - // List of unwanted IP ranges in CIDR format, e.g. "123.45.67.89/24". - private const BAD_IP_RANGES = [ - '127.0.0.1/32', - ]; - - /** - * How should this module be identified in the control panel, etc.? - * - * @return string - */ - public function title(): string - { - return 'My custom middleware'; - } - - /** - * A sentence describing what this module does. - * - * @return string - */ - public function description(): string - { - return 'This is an example of middleware'; - } - - /** - * Code here is executed before and after we process the request/response. - * We can block access by throwing an exception. - * - * @param ServerRequestInterface $request - * @param RequestHandlerInterface $handler - * - * @return ResponseInterface - */ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - // Code here is executed before we process the request/response. - - $ip_address = $request->getAttribute('client-ip'); - foreach (self::BAD_IP_RANGES as $bad_cidr) { - if ($this->ipInCidr($ip_address, $bad_cidr)) { - return response('IP address is not allowed: ' . $bad_cidr, 403); - } - } - - $user_agent = $request->getHeaderLine('HTTP_USER_AGENT'); - foreach (self::BAD_USER_AGENTS as $bad_user_agent) { - if (preg_match($bad_user_agent, $user_agent)) { - return response('User agent is not allowed: ' . $bad_user_agent, 403); - } - } - - // Generate the response. - $response = $handler->handle($request); - - // Code here is executed after we process the request/response. - // We can also modify the response. - $response = $response->withHeader('X-Powered-By', 'Fish'); - - return $response; - } - - /** - * Is an IP address in a CIDR range> - * - * @param string $ip - * @param string $cidr - * - * @return bool - */ - private function ipInCidr(string $ip, string $cidr): bool - { - [$net, $mask] = explode('/', $cidr); - - $ip_net = ip2long($net); - $ip_mask = ~((1 << 32 - (int) $mask) - 1); - $ip_ip = ip2long($ip); - - return ($ip_ip & $ip_mask) === ($ip_net & $ip_mask); - } -}; diff --git a/modules_v4/example-report.disable/module.php b/modules_v4/example-report.disable/module.php deleted file mode 100644 index acde827286..0000000000 --- a/modules_v4/example-report.disable/module.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php - -/** - * Example report. - */ - -declare(strict_types=1); - -namespace MyCustomNamespace; - -use Fisharebest\Webtrees\Module\AbstractModule; -use Fisharebest\Webtrees\Module\ModuleCustomInterface; -use Fisharebest\Webtrees\Module\ModuleCustomTrait; -use Fisharebest\Webtrees\Module\ModuleReportInterface; -use Fisharebest\Webtrees\Module\ModuleReportTrait; - -return new class extends AbstractModule implements ModuleCustomInterface, ModuleReportInterface { - use ModuleCustomTrait; - use ModuleReportTrait; - - /** - * @return string - */ - public function title(): string - { - return 'Custom report'; - } - - /** - * Where does this module store its resources - * - * @return string - */ - public function resourcesFolder(): string - { - return __DIR__ . '/resources/'; - } - - /** - * Name of the XML report file, relative to the resources folder. - * - * @return string - */ - public function xmlFilename(): string - { - return 'report.xml'; - } -}; diff --git a/modules_v4/example-report.disable/resources/report.xml b/modules_v4/example-report.disable/resources/report.xml deleted file mode 100644 index cd4ba0cd5f..0000000000 --- a/modules_v4/example-report.disable/resources/report.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<Report> - <Title><var var="I18N::translate('Report')" /></Title> - <Description><var var="I18N::translate('Report')" /></Description> - - <Style name="text" size="10"/> - <Doc> - <Body> - </Body> - </Doc> -</Report> diff --git a/modules_v4/example-server-configuration.disable/ExampleServerConfigurationModule.php b/modules_v4/example-server-configuration.disable/ExampleServerConfigurationModule.php deleted file mode 100644 index bc0d3c6ac1..0000000000 --- a/modules_v4/example-server-configuration.disable/ExampleServerConfigurationModule.php +++ /dev/null @@ -1,143 +0,0 @@ -<?php - -/** - * An example module to modify PHP and database configuration. - */ - -declare(strict_types=1); - -namespace MyCustomNamespace; - -use Fisharebest\Webtrees\Module\AbstractModule; -use Fisharebest\Webtrees\Module\ModuleCustomInterface; -use Fisharebest\Webtrees\Module\ModuleCustomTrait; -use Fisharebest\Webtrees\Services\ServerCheckService; -use Illuminate\Database\Capsule\Manager as DB; - -class ExampleServerConfigurationModule extends AbstractModule implements ModuleCustomInterface -{ - use ModuleCustomTrait; - - /** @var ServerCheckService */ - private $server_check_service; - - /** - * Constructor. - * - * @param ServerCheckService $server_check_service - */ - public function __construct(ServerCheckService $server_check_service) - { - $this->server_check_service = $server_check_service; - } - - /** - * How should this module be identified in the control panel, etc.? - * - * @return string - */ - public function title(): string - { - return 'Server configuration'; - } - - /** - * A sentence describing what this module does. - * - * @return string - */ - public function description(): string - { - return 'Modify the server configuration'; - } - - /** - * The person or organisation who created this module. - * - * @return string - */ - public function customModuleAuthorName(): string - { - return 'Your name'; - } - - /** - * If you do not have access to the PHP.INI or MYSQL.CNF files on your server, then - * you may be able to change them. - */ - public function boot(): void - { - // IMPORTANT - not all servers allow you to change these settings. Sometimes, even - // attempting to change them can result in your script being terminated immediately. - // We attempt to detect whether this will happen, but it is not possible to - // do so with 100% accuracy. - - if (!$this->server_check_service->isFunctionDisabled('ini_set')) { - $this->phpIni(); - } - - if (!$this->server_check_service->isFunctionDisabled('set_time_limit')) { - $this->phpTimeLimit(); - } - - if (!$this->server_check_service->isFunctionDisabled('putenv')) { - $this->phpEnvironment(); - } - - if (DB::connection()->getDriverName() === 'mysql') { - $this->mysql(); - } - } - - /** - * Modify the PHP time limit. - */ - private function phpTimeLimit(): void - { - // Set the time limit for PHP scripts. - // Recommended settings are between 15 and 60 seconds. - // - // Typical webservers will not wait more than 60 seconds for a PHP response, - // so it is pointless to allow the server to continue using resources for - // a request that will be ignored. - - //set_time_limit(45); - } - - /** - * Modify the PHP environment variables. - */ - private function phpEnvironment(): void - { - // Some servers block access to the system temporary folder using open_basedir... - // - // Create a temporary folder somewhere we have read/write access, and tell PHP to use it. - //$tmp = __DIR__ . '/../../data/tmp'; - //if (!is_dir($tmp)) { - // mkdir($tmp); - //} - //putenv('TMPDIR=' . $tmp); - } - - /** - * Modify the PHP.INI settings. - */ - private function phpIni(): void - { - // Set the maximum amount of memory that PHP scripts can use. - // Recommended settings are between 128M and 1024M - - //ini_set('memory_limit', '256M'); - } - - /** - * Modify the MySQL connection. - */ - private function mysql(): void - { - // If you get the error "The SELECT would examine more than MAX_JOIN_SIZE rows", - // then setting this option may help. - - //DB::statement('SET SESSION sql_big_selects := 1'); - } -} diff --git a/modules_v4/example-server-configuration.disable/module.php b/modules_v4/example-server-configuration.disable/module.php deleted file mode 100644 index 1442354391..0000000000 --- a/modules_v4/example-server-configuration.disable/module.php +++ /dev/null @@ -1,19 +0,0 @@ -<?php - -/** - * An example module to modify PHP and database configuration. - */ - -declare(strict_types=1); - -namespace MyCustomNamespace; - -// Unlike the other examples, this one has a separate file for the class definition. -// This is because the constructor has some dependencies, so we must create it -// with "app(CustomModule::class)" rather than "new CustomModule()". -// This means we can't use an anonymous class, and our coding standards -// mean that the class needs to go in its own file. -// For simple modules, it might be easier to declare the class here. -require __DIR__ . '/ExampleServerConfigurationModule.php'; - -return app(ExampleServerConfigurationModule::class); diff --git a/modules_v4/example-theme.disable/module.php b/modules_v4/example-theme.disable/module.php deleted file mode 100644 index b0f7d4b3e0..0000000000 --- a/modules_v4/example-theme.disable/module.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php - -/** - * Example theme. Here we are extending an existing theme. - * Instead, you could extend AbstractModule and implement ModuleThemeInterface directly. - */ - -declare(strict_types=1); - -namespace MyCustomNamespace; - -use Fisharebest\Webtrees\Module\MinimalTheme; -use Fisharebest\Webtrees\Module\ModuleCustomInterface; -use Fisharebest\Webtrees\Module\ModuleCustomTrait; -use Fisharebest\Webtrees\View; - -return new class extends MinimalTheme implements ModuleCustomInterface { - use ModuleCustomTrait; - - /** - * @return string - */ - public function title(): string - { - return 'Custom theme'; - } - - /** - * Bootstrap the module - */ - public function boot(): void - { - // Register a namespace for our views. - View::registerNamespace($this->name(), $this->resourcesFolder() . 'views/'); - - // Replace an existing view with our own version. - View::registerCustomView('::chart-box', $this->name() . '::chart-box'); - } - - /** - * Where does this module store its resources - * - * @return string - */ - public function resourcesFolder(): string - { - return __DIR__ . '/resources/'; - } - - /** - * Add our own stylesheet to the existing stylesheets. - * - * @return array<string> - */ - public function stylesheets(): array - { - $stylesheets = parent::stylesheets(); - - // NOTE - a future version of webtrees will allow the modules to be stored in a private folder. - // Only files in the /public/ folder will be accessible via the webserver. - // Since modules cannot copy their files to the /public/ folder, they need to provide them via a callback. - $stylesheets[] = $this->assetUrl('css/theme.css'); - - return $stylesheets; - } -}; diff --git a/modules_v4/example-theme.disable/resources/css/theme.css b/modules_v4/example-theme.disable/resources/css/theme.css deleted file mode 100644 index 0d79f0bc0a..0000000000 --- a/modules_v4/example-theme.disable/resources/css/theme.css +++ /dev/null @@ -1,3 +0,0 @@ -body.wt-global { - color: orange; -} diff --git a/modules_v4/example-theme.disable/resources/views/chart-box.phtml b/modules_v4/example-theme.disable/resources/views/chart-box.phtml deleted file mode 100644 index 88e68a0201..0000000000 --- a/modules_v4/example-theme.disable/resources/views/chart-box.phtml +++ /dev/null @@ -1,5 +0,0 @@ -<!-- A nice orange border around the existing chart box --> -<div style="outline: dashed thick orange"> - <!-- The "::" allows us to use the original view, without being redirected to this file. --> - <?= view('::chart-box', ['individual' => $individual]) ?> -</div> diff --git a/modules_v4/example.disable/module.php b/modules_v4/example.disable/module.php deleted file mode 100644 index 2f13f9b39e..0000000000 --- a/modules_v4/example.disable/module.php +++ /dev/null @@ -1,160 +0,0 @@ -<?php - -/** - * Example module. - */ - -declare(strict_types=1); - -namespace MyCustomNamespace; - -use Fisharebest\Localization\Translation; -use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\AbstractModule; -use Fisharebest\Webtrees\Module\ModuleCustomInterface; -use Fisharebest\Webtrees\Module\ModuleCustomTrait; - -return new class extends AbstractModule implements ModuleCustomInterface { - use ModuleCustomTrait; - - /** - * Constructor. The constructor is called on *all* modules, even ones that are disabled. - * This is a good place to load business logic ("services"). Type-hint the parameters and - * they will be injected automatically. - */ - public function __construct() - { - // NOTE: If your module is dependent on any of the business logic ("services"), - // then you would type-hint them in the constructor and let webtrees inject them - // for you. However, we can't use dependency injection on anonymous classes like - // this one. For an example of this, see the example-server-configuration module. - } - - /** - * Bootstrap. This function is called on *enabled* modules. - * It is a good place to register routes and views. - * - * @return void - */ - public function boot(): void - { - } - - /** - * How should this module be identified in the control panel, etc.? - * - * @return string - */ - public function title(): string - { - return 'My custom module'; - } - - /** - * A sentence describing what this module does. - * - * @return string - */ - public function description(): string - { - return 'This module doesn‘t do anything'; - } - - /** - * The person or organisation who created this module. - * - * @return string - */ - public function customModuleAuthorName(): string - { - return 'Greg Roach'; - } - - /** - * The version of this module. - * - * @return string - */ - public function customModuleVersion(): string - { - return '1.0.0'; - } - - /** - * A URL that will provide the latest version of this module. - * - * @return string - */ - public function customModuleLatestVersionUrl(): string - { - return 'https://www.example.com/latest-version.txt'; - } - - /** - * Where to get support for this module. Perhaps a github repository? - * - * @return string - */ - public function customModuleSupportUrl(): string - { - return 'https://www.example.com/support'; - } - - /** - * Additional/updated translations. - * - * @param string $language - * - * @return array<string> - */ - public function customTranslations(string $language): array - { - switch ($language) { - case 'en-AU': - case 'en-GB': - case 'en-US': - return $this->englishTranslations(); - - case 'fr': - case 'fr-CA': - return $this->frenchTranslations(); - - case 'some-other-language': - // Arrays are preferred, and faster. - // If your module uses .MO files, then you can convert them to arrays like this. - return (new Translation('path/to/file.mo'))->asArray(); - - default: - return []; - } - } - - /** - * @return array<string,string> - */ - protected function englishTranslations(): array - { - // Note the special characters used in plural and context-sensitive translations. - return [ - 'Individual' => 'Fish', - 'Individuals' => 'Fishes', - '%s individual' . I18N::PLURAL . '%s individuals' => '%s fish' . I18N::PLURAL . '%s fishes', - 'Unknown given name' . I18N::CONTEXT . '…' => '?fish?', - 'Unknown surname' . I18N::CONTEXT . '…' => '?FISH?', - ]; - } - - /** - * @return array<string,string> - */ - protected function frenchTranslations(): array - { - return [ - 'Individual' => 'Poisson', - 'Individuals' => 'Poissons', - '%s individual' . I18N::PLURAL . '%s individuals' => '%s poisson' . I18N::PLURAL . '%s poissons', - 'Unknown given name' . I18N::CONTEXT . '…' => '?poission?', - 'Unknown surname' . I18N::CONTEXT . '…' => '?POISSON?', - ]; - } -}; |
