Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/i18n-validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: I18n Validate

on:
pull_request:
paths:
- 'resources/translations/**/*.xlf'
- 'composer.lock'
- 'composer.json'

jobs:
validate-xliff:
runs-on: ubuntu-22.04

strategy:
fail-fast: false
matrix:
php: ['8.1']

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: imap, zip
tools: composer:v2
coverage: none

- name: Cache Composer packages
uses: actions/cache@v4
with:
path: |
~/.composer/cache/files
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-${{ matrix.php }}-

- name: Install dependencies (no dev autoloader scripts)
run: |
set -euo pipefail
composer install --no-interaction --no-progress --prefer-dist

- name: Lint XLIFF with Symfony
run: |
set -euo pipefail
# Adjust the directory to match your repo layout
php bin/console lint:xliff resources/translations

- name: Validate XLIFF XML with xmllint
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends libxml2-utils
# Adjust root dir; prune vendor; accept spaces/newlines safely
find resources/translations -type f -name '*.xlf' -not -path '*/vendor/*' -print0 \
| xargs -0 -n1 xmllint --noout

- name: Symfony translation sanity (extract dry-run)
run: |
set -euo pipefail
# Show what would be created/updated without writing files
php bin/console translation:extract en \
--format=xlf \
--domain=messages \
--dump-messages \
--no-interaction
# Note: omit --force to keep this a dry-run
23 changes: 23 additions & 0 deletions .weblate
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# .weblate
---
projects:
- slug: phplist-core
name: phpList core
components:
- slug: messages
name: Messages
files:
# {language} is Weblate’s placeholder (e.g., fr, de, es)
- src: resources/translations/messages.en.xlf
template: true
# Where localized files live (mirrors Symfony layout)
target: resources/translations/messages.{language}.xlf
file_format: xliff
language_code_style: bcp
# Ensure placeholders like %name% are preserved
parse_file_headers: true
check_flags:
- xml-invalid
- placeholders
- urls
- accelerated
5 changes: 4 additions & 1 deletion config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ parameters:

framework:
#esi: ~
#translator: { fallbacks: ['%locale%'] }
translator:
default_path: '%kernel.project_dir%/resources/translations'
fallbacks: ['%locale%']

secret: '%secret%'
router:
resource: '%kernel.project_dir%/config/routing.yml'
Expand Down
12 changes: 8 additions & 4 deletions config/services/managers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ services:
autoconfigure: true
public: false

PhpList\Core\Domain\Configuration\Service\Manager\ConfigManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Configuration\Service\Manager\EventLogManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Identity\Service\SessionManager:
autowire: true
autoconfigure: true
Expand Down Expand Up @@ -80,10 +88,6 @@ services:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Configuration\Service\Manager\ConfigManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Messaging\Service\Manager\BounceRuleManager:
autowire: true
autoconfigure: true
Expand Down
15 changes: 10 additions & 5 deletions config/services/repositories.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
services:
PhpList\Core\Domain\Configuration\Repository\ConfigRepository:
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
arguments:
- PhpList\Core\Domain\Configuration\Model\Config

PhpList\Core\Domain\Configuration\Repository\EventLogRepository:
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
arguments:
- PhpList\Core\Domain\Configuration\Model\EventLog

PhpList\Core\Domain\Identity\Repository\AdministratorRepository:
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
arguments:
Expand Down Expand Up @@ -66,11 +76,6 @@ services:
arguments:
- PhpList\Core\Domain\Messaging\Model\TemplateImage

PhpList\Core\Domain\Configuration\Repository\ConfigRepository:
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
arguments:
- PhpList\Core\Domain\Configuration\Model\Config

PhpList\Core\Domain\Messaging\Repository\UserMessageBounceRepository:
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
arguments:
Expand Down
7 changes: 7 additions & 0 deletions config/services/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,10 @@ services:
PhpList\Core\Domain\Messaging\Service\BounceActionResolver:
arguments:
- !tagged_iterator { tag: 'phplist.bounce_action_handler' }

# I18n
PhpList\Core\Domain\Common\I18n\SimpleTranslator:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Common\I18n\TranslatorInterface: '@PhpList\Core\Domain\Common\I18n\SimpleTranslator'
44 changes: 44 additions & 0 deletions resources/translations/messages.en.xlf
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" target-language="en" datatype="plaintext" original="messages">
<body>

<!-- Authentication -->
<trans-unit id="auth.not_authorized">
<source>Not authorized</source>
<target>Not authorized</target>
</trans-unit>

<trans-unit id="auth.login_failed">
<source>Failed admin login attempt for '%login%'</source>
<target>Failed admin login attempt for '%login%'</target>
</trans-unit>

<trans-unit id="auth.login_disabled">
<source>Login attempt for disabled admin '%login%'</source>
<target>Login attempt for disabled admin '%login%'</target>
</trans-unit>

<!-- Identity -->
<trans-unit id="identity.admin_not_found">
<source>Administrator not found</source>
<target>Administrator not found</target>
</trans-unit>

<!-- Subscription -->
<trans-unit id="subscription.list_not_found">
<source>Subscriber list not found.</source>
<target>Subscriber list not found.</target>
</trans-unit>
<trans-unit id="subscription.subscriber_not_found">
<source>Subscriber does not exists.</source>
<target>Subscriber does not exists.</target>
</trans-unit>
<trans-unit id="subscription.not_found_for_list_and_subscriber">
<source>Subscription not found for this subscriber and list.</source>
<target>Subscription not found for this subscriber and list.</target>
</trans-unit>

</body>
</file>
</xliff>
29 changes: 29 additions & 0 deletions src/Domain/Common/I18n/Messages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace PhpList\Core\Domain\Common\I18n;

/**
* Centralized message keys to be used across the application.
* These keys map to translation strings in resources/translations.
*/
final class Messages
{
// Authentication / Authorization
public const AUTH_NOT_AUTHORIZED = 'auth.not_authorized';
public const AUTH_LOGIN_FAILED = 'auth.login_failed';
public const AUTH_LOGIN_DISABLED = 'auth.login_disabled';

// Identity
public const IDENTITY_ADMIN_NOT_FOUND = 'identity.admin_not_found';

// Subscription
public const SUBSCRIPTION_LIST_NOT_FOUND = 'subscription.list_not_found';
public const SUBSCRIPTION_SUBSCRIBER_NOT_FOUND = 'subscription.subscriber_not_found';
public const SUBSCRIPTION_NOT_FOUND_FOR_LIST_AND_SUBSCRIBER = 'subscription.not_found_for_list_and_subscriber';

private function __construct()
{
}
}
33 changes: 33 additions & 0 deletions src/Domain/Configuration/Model/Filter/EventLogFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace PhpList\Core\Domain\Configuration\Model\Filter;

use DateTimeInterface;
use PhpList\Core\Domain\Common\Model\Filter\FilterRequestInterface;

class EventLogFilter implements FilterRequestInterface
{
public function __construct(
private readonly ?string $page = null,
private readonly ?DateTimeInterface $dateFrom = null,
private readonly ?DateTimeInterface $dateTo = null,
) {
}

public function getPage(): ?string
{
return $this->page;
}

public function getDateFrom(): ?DateTimeInterface
{
return $this->dateFrom;
}

public function getDateTo(): ?DateTimeInterface
{
return $this->dateTo;
}
}
5 changes: 5 additions & 0 deletions src/Domain/Configuration/Model/I18n.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
use PhpList\Core\Domain\Common\Model\Interfaces\DomainModel;
use PhpList\Core\Domain\Configuration\Repository\I18nRepository;

/**
* @deprecated
*
* Symfony\Contracts\Translation will be used instead.
*/
#[ORM\Entity(repositoryClass: I18nRepository::class)]
#[ORM\Table(name: 'phplist_i18n')]
#[ORM\UniqueConstraint(name: 'lanorigunq', columns: ['lan', 'original'])]
Expand Down
37 changes: 37 additions & 0 deletions src/Domain/Configuration/Repository/EventLogRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,48 @@

namespace PhpList\Core\Domain\Configuration\Repository;

use InvalidArgumentException;
use PhpList\Core\Domain\Common\Model\Filter\FilterRequestInterface;
use PhpList\Core\Domain\Common\Repository\AbstractRepository;
use PhpList\Core\Domain\Common\Repository\CursorPaginationTrait;
use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface;
use PhpList\Core\Domain\Configuration\Model\Filter\EventLogFilter;
use PhpList\Core\Domain\Configuration\Model\EventLog;

class EventLogRepository extends AbstractRepository implements PaginatableRepositoryInterface
{
use CursorPaginationTrait;

/**
* @return EventLog[]
* @throws InvalidArgumentException
*/
public function getFilteredAfterId(int $lastId, int $limit, ?FilterRequestInterface $filter = null): array
{
$queryBuilder = $this->createQueryBuilder('e')
->andWhere('e.id > :lastId')
->setParameter('lastId', $lastId)
->orderBy('e.id', 'ASC')
->setMaxResults($limit);

if ($filter === null) {
return $queryBuilder->getQuery()->getResult();
}

if (!$filter instanceof EventLogFilter) {
throw new InvalidArgumentException('Expected EventLogFilter.');
}

if ($filter->getPage() !== null) {
$queryBuilder->andWhere('e.page = :page')->setParameter('page', $filter->getPage());
}
if ($filter->getDateFrom() !== null) {
$queryBuilder->andWhere('e.entered >= :dateFrom')->setParameter('dateFrom', $filter->getDateFrom());
}
if ($filter->getDateTo() !== null) {
$queryBuilder->andWhere('e.entered <= :dateTo')->setParameter('dateTo', $filter->getDateTo());
}

return $queryBuilder->getQuery()->getResult();
}
}
1 change: 1 addition & 0 deletions src/Domain/Configuration/Repository/I18nRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PhpList\Core\Domain\Common\Repository\AbstractRepository;

/** @deprecated */
class I18nRepository extends AbstractRepository
{
}
Loading
Loading