Skip to content

Translations and test app #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 9, 2025
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
54 changes: 54 additions & 0 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Project

on:
pull_request:
push:
branches:
- main
- develop

jobs:
check-assets:
name: Check that assets are up to date
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: arduino/setup-task@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Build assets
run: task assets:build

- name: Check for changes in built assets
run: git diff --diff-filter=ACMRT --exit-code build

check-translations:
name: Check that translations are up to date
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: arduino/setup-task@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Extract translations
run: task translations:extract

- name: Check for changes in built assets
run: git diff --diff-filter=ACMRT --exit-code translations

check-shell-scripts:
name: Check shell scripts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: arduino/setup-task@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Build assets
run: task coding-standards:shell:check
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- [PR-3](https://github.com/itk-dev/tidy-feedback/pull/3)
Added translations and standalone app
- [PR-5](https://github.com/itk-dev/tidy-feedback/pull/5)
Add styling. Make region and form draggable

Expand Down
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ drush tidy-feedback:doctrine:schema-update

### Symfony

Create `config/routes/tidy_feedback.yaml`:
Create `config/routes/tidy_feedback.yaml` (or copy
[`resources/config/routes/tidy_feedback.yaml`](resources/config/routes/tidy_feedback.yaml)):

``` yaml
#config/routes/tidy_feedback.yaml
Expand All @@ -34,7 +35,8 @@ tidy_feedback:
```

> [!NOTE]
> You can use any `prefix`, but for consistency with the Drupal version of Tidy feedback you should use `/tidy-feedback`.
> You can use any path as `prefix`, but for consistency with the Drupal version of Tidy feedback you should use
> `/tidy-feedback`.

If [Symfony Flex](https://symfony.com/doc/current/setup/flex.html) hasn't already done so, you must enable the
TidyFeedbackBundle bundle:
Expand All @@ -60,14 +62,14 @@ in the environment variable `TIDY_FEEDBACK_DATABASE_URL`, e.g.
``` dotenv
# .env
# See https://www.doctrine-project.org/projects/doctrine-dbal/en/4.2/reference/configuration.html#connecting-using-a-url for details.
TIDY_FEEDBACK_DATABASE_URL="pdo-sqlite:////app/tidy-feedback.sqlite"
TIDY_FEEDBACK_DATABASE_URL=pdo-sqlite://localhost//app/tidy-feedback.sqlite
```

As an alternative for Drupal you can set `TIDY_FEEDBACK_DATABASE_URL` in `settings.local.php`:

``` php
# web/sites/default/settings.local.php
putenv('TIDY_FEEDBACK_DATABASE_URL=pdo-sqlite:////app/tidy-feedback.sqlite');
putenv('TIDY_FEEDBACK_DATABASE_URL=pdo-sqlite://localhost//app/tidy-feedback.sqlite');
```

`TIDY_FEEDBACK_USERS='{"admin": "password"}'`
Expand Down Expand Up @@ -118,3 +120,17 @@ In order to make this behave as both a Drupal module and a Synfony bundle, we us
### Twig

We use a watered-down instance of Twig with only a `trans` filter and a `path` function.

### Development app

For development, you can start a Symfony app with Tidy feedback installed:

``` shell
task app:start
```

Run `task app:stop` to stop the app.

> [!TIP]
> Use `tidy-feedback-show=form` in the query string, e.g. `/tidy-feedback/test?tidy-feedback-show=form`, to
> automatically show the feedback form when loading a page.
52 changes: 50 additions & 2 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ dotenv: [".env.local", ".env"]

vars:
DOCKER_COMPOSE: '{{.TASK_DOCKER_COMPOSE | default "docker compose"}}'
APP_DOCKER_COMPOSE: docker compose --profile app

tasks:
compose:
Expand All @@ -22,16 +23,21 @@ tasks:
- task: npm
vars:
TASK_ARGS: install
- task compose -- run --tty --interactive --rm node node_modules/.bin/encore production --progress
- task compose -- run --interactive --rm node node_modules/.bin/encore production --progress
silent: true

assets:watch:
desc: Build assets and watch for changes
cmds:
- task: assets:build
- task compose -- run --tty --interactive --rm node node_modules/.bin/encore dev --watch
- task compose -- run --interactive --rm node node_modules/.bin/encore dev --watch
silent: true

translations:extract:
cmds:
- task compose -- run --rm symfony /app/task/scripts/translations-extract
- task: coding-standards:yaml:apply

npm:
cmds:
- task compose -- run --rm node npm {{.TASK_ARGS}} {{.CLI_ARGS}}
Expand Down Expand Up @@ -110,6 +116,16 @@ tasks:
- task compose -- run --rm phpfpm vendor/bin/phpcs
silent: true

coding-standards:shell:check:
desc: "Check coding standards for shell scripts"
cmds:
# https://github.com/koalaman/shellcheck?tab=readme-ov-file#installing
- docker run --rm --volume "$PWD:"/mnt koalaman/shellcheck:stable {{.GLOB}}
vars:
# We use a block scalar (https://yaml-multiline.info/#block-scalars) here to make escaping (a little) easier.
GLOB: >-
{{.CLI_ARGS | default "task/scripts/*"}}

coding-standards:twig:apply:
desc: "Apply coding standards for Twig"
cmds:
Expand Down Expand Up @@ -142,3 +158,35 @@ tasks:
cmds:
- task --list-all
silent: true

app:start:
desc: Start Symfony app with Tity feedback installed
cmds:
- |
echo ------------------------------------------------------------------------------------------------
echo For reasons yet unknown, you may have to run this command 2 or 3 times to make everything work …
echo ------------------------------------------------------------------------------------------------

- task app:compose -- pull
- task app:compose -- up --detach --remove-orphans --wait

- task app:compose -- exec symfony composer config repositories.tidy-feedback path /app
# https://github.com/itk-dev/tidy-feedback?tab=readme-ov-file#installation
- task app:compose -- exec symfony composer require itk-dev/tidy-feedback:dev-main

# https://github.com/itk-dev/tidy-feedback?tab=readme-ov-file#symfony
- task app:compose -- exec symfony cp /app/resources/config/routes/tidy_feedback.yaml config/routes/
- task app:compose -- exec symfony bin/console cache:clear

- task app:compose -- exec symfony bin/console tidy-feedback:doctrine:schema-update
- open "http://$(task app:compose -- port symfony 80)/tidy-feedback/test?tidy-feedback-show=form"
silent: true

app:stop:
desc: Stop Symfony app
cmds:
- task app:compose -- down --remove-orphans

app:compose:
cmds:
- "{{.APP_DOCKER_COMPOSE}} {{.CLI_ARGS}}"
24 changes: 24 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,27 @@ services:
- .:/app
profiles:
- dev

symfony:
# https://hub.docker.com/r/shinsenter/symfony
image: shinsenter/symfony:latest
# For some reason it takes a while before composer.json appear in the project root
# @todo Investigate why
healthcheck:
test: ["CMD", "test", "-f", "composer.json"]
interval: 2s
timeout: 60s
retries: 20
volumes:
- ./:/app
profiles:
- dev
- app
ports:
- "80"
environment:
APP_ENV: dev
# Needed to run without requiring a cache
TIDY_FEEDBACK_DEV_MODE: true
TIDY_FEEDBACK_DEBUG: true
TIDY_FEEDBACK_DATABASE_URL: pdo-sqlite://localhost//app/app-tidy-feedback.sqlite
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"doctrine/dbal": "^3 || ^4",
"doctrine/orm": "^2.8 || ^3",
"symfony/cache": "^6 || ^7",
"symfony/http-kernel": "^6 || ^7",
"twig/twig": "^3"
},
"require-dev": {
Expand Down
3 changes: 3 additions & 0 deletions resources/config/routes/tidy_feedback.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tidy_feedback:
resource: "@TidyFeedbackBundle/config/routes.php"
prefix: /tidy-feedback
80 changes: 61 additions & 19 deletions src/TidyFeedbackHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Yaml\Yaml;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFilter;
Expand Down Expand Up @@ -62,7 +63,8 @@ public function renderTemplate(string $path, array $context = []): string
// @todo
// 'cache' => '/tmp/twig_compilation_cache',
]);
self::$twig->addFilter(new TwigFilter('trans', fn (string $text) => $text));

self::$twig->addFilter(new TwigFilter('trans', $this->trans(...)));
self::$twig->addFunction(new TwigFunction('path', fn (string $name, array $parameters = []) => $this->urlGenerator->generate($name, $parameters)));
}

Expand All @@ -71,6 +73,30 @@ public function renderTemplate(string $path, array $context = []): string
return $template->render($context);
}

private static array $translations;

private function trans(string $text, array $context = []): string
{
if (!isset(self::$translations)) {
self::$translations = [];
foreach (glob(__DIR__.'/../translations/*.yaml') as $file) {
if (preg_match('/\.(?P<locale>[^.]+)\.yaml$/', $file, $matches)) {
$locale = $matches['locale'];
try {
self::$translations[$locale] = Yaml::parseFile($file);
} catch (\Exception) {
}
}
}
}

// @todo Get the locale from some context …
$locale = self::getConfig('default_locale');
$fallbackLocale = 'en';

return self::$translations[$locale][$text] ?? self::$translations[$fallbackLocale][$text] ?? $text;
}

private static EntityManager $entityManager;

/**
Expand All @@ -79,11 +105,13 @@ public function renderTemplate(string $path, array $context = []): string
public static function getEntityManager(): EntityManagerInterface
{
if (empty(self::$entityManager)) {
$config = ORMSetup::createAttributeMetadataConfiguration(
$config = ORMSetup::createAttributeMetadataConfig(
paths: [__DIR__.'/Model'],
isDevMode: (bool) ($_ENV['TIDY_FEEDBACK_DEV_MODE'] ?? false),
);
$config->enableNativeLazyObjects(true);

$dsn = static::getConfig('database_url');
$dsn = self::getConfig('database_url');
$connectionParams = (new DsnParser())->parse($dsn);
$connection = DriverManager::getConnection($connectionParams, $config);

Expand All @@ -101,13 +129,22 @@ public function createAssetResponse(string $asset): Response
throw new NotFoundHttpException();
}

return new BinaryFileResponse($filename, headers: [
$response = new BinaryFileResponse($filename, headers: [
'content-type' => match (pathinfo($filename, PATHINFO_EXTENSION)) {
'css' => 'text/css',
'js' => 'text/javascript',
default => throw new NotFoundHttpException(),
},
]);
],
autoEtag: true, autoLastModified: true
);

if (self::getConfig('debug')) {
// setExpires(null) does not seem to work as advertised, so we use a date in the far past.
$response->setExpires(new \DateTimeImmutable('2001-01-01'));
}

return $response;
}

public static function updateSchema(OutputInterface $output): bool
Expand All @@ -131,24 +168,29 @@ public static function updateSchema(OutputInterface $output): bool
return false;
}

private static array $config;

private static function getConfig(?string $name): mixed
{
$getEnv = static fn (string $name) => getenv($name) ?: ($_ENV[$name] ?? null);

$config = [
// https://www.doctrine-project.org/projects/doctrine-dbal/en/4.2/reference/configuration.html#connecting-using-a-url
'database_url' => $getEnv('TIDY_FEEDBACK_DATABASE_URL'),
'debug' => (bool) $getEnv('TIDY_FEEDBACK_DEBUG'),
];

if ($users = $getEnv('TIDY_FEEDBACK_USERS')) {
try {
$config['users'] = json_decode($users, true, flags: JSON_THROW_ON_ERROR);
} catch (\Throwable) {
if (!isset(self::$config)) {
$getEnv = static fn (string $name) => getenv($name) ?: ($_ENV[$name] ?? null);

self::$config = [
// https://www.doctrine-project.org/projects/doctrine-dbal/en/4.2/reference/configuration.html#connecting-using-a-url
'database_url' => $getEnv('TIDY_FEEDBACK_DATABASE_URL'),
'debug' => (bool) $getEnv('TIDY_FEEDBACK_DEBUG'),
'default_locale' => $getEnv('TIDY_FEEDBACK_DEFAULT_LOCALE') ?? 'en',
];

if ($users = $getEnv('TIDY_FEEDBACK_USERS')) {
try {
$config['users'] = json_decode($users, true, flags: JSON_THROW_ON_ERROR);
} catch (\Throwable) {
}
}
}

return $name ? ($config[$name] ?? null) : $config;
return $name ? (self::$config[$name] ?? null) : self::$config;
}

public static function getSubscribedEvents(): array
Expand Down Expand Up @@ -183,7 +225,7 @@ public function onKernelResponse(ResponseEvent $event): void
$response->setContent($content);
}
} catch (\Throwable $throwable) {
if (static::getConfig('debug')) {
if (self::getConfig('debug')) {
throw $throwable;
}
// Ignore all errors!
Expand Down
Loading