@rt-tools/agent-kit 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/checks/board-long-work.github.mjs +101 -0
- package/assets/checks/board-runs.github.mjs +34 -0
- package/assets/checks/check-board.github.mjs +16 -2
- package/assets/checks/check-reuse.mjs +4 -1
- package/assets/checks/check-schema-drift.mjs +65 -6
- package/assets/checks/rt-kit-checks.config.mjs +13 -0
- package/assets/checks/signals.mjs +41 -1
- package/assets/commands/next-session.md +16 -5
- package/assets/defaults/project.sh +16 -0
- package/assets/hooks/browser-guard-no-asking.sh +5 -1
- package/assets/hooks/git-guard-delivery.sh +15 -11
- package/assets/hooks/git-guard-push-tests.sh +22 -0
- package/assets/hooks/glossary-load.sh +23 -2
- package/assets/hooks/grill-gate.sh +62 -0
- package/assets/laws/autonomous-work.md +30 -0
- package/assets/laws/project-documentation.md +8 -0
- package/assets/patterns/autonomous-work-run.md +105 -0
- package/assets/patterns/browser-verification-stand.md +1 -1
- package/assets/patterns/doc-style-write.md +16 -0
- package/assets/patterns/git-workflow-pr-ready.md +93 -0
- package/assets/patterns/git-workflow-pr.azure.md +1 -1
- package/assets/patterns/git-workflow-pr.github.md +1 -1
- package/assets/patterns/git-workflow-pr.gitlab.md +1 -1
- package/assets/patterns/task-flow-start.md +47 -47
- package/assets/patterns/ts-procedure.md +3 -2
- package/assets/rules/autonomous-work.md +92 -0
- package/assets/rules/browser-verification.md +6 -0
- package/assets/rules/deploy-flow.azure.md +7 -0
- package/assets/rules/deploy-flow.github.md +7 -0
- package/assets/rules/deploy-flow.gitlab.md +7 -0
- package/assets/rules/doc-style.md +18 -0
- package/assets/rules/git-workflow.azure.md +8 -0
- package/assets/rules/git-workflow.github.md +44 -45
- package/assets/rules/git-workflow.gitlab.md +8 -0
- package/assets/rules/reuse-first.md +17 -3
- package/assets/rules/task-flow.md +62 -63
- package/assets/skills/agent-kit.md +20 -20
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +6 -0
- package/lib/commands.js.map +1 -1
- package/lib/push-gate.d.ts +14 -0
- package/lib/push-gate.d.ts.map +1 -0
- package/lib/push-gate.js +93 -0
- package/lib/push-gate.js.map +1 -0
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.23.0.tgz +0 -0
- package/assets/laws/application/access.md +0 -34
- package/assets/laws/application/locales.md +0 -33
- package/assets/laws/application/search-visibility.md +0 -24
- package/assets/patterns/permissions-procedure.md +0 -71
- package/assets/patterns/seo-page.md +0 -104
- package/assets/patterns/seo-verify.md +0 -83
- package/assets/patterns/translations-content.md +0 -107
- package/assets/patterns/translations-key.md +0 -64
- package/assets/rules/permissions.md +0 -116
- package/assets/rules/seo.md +0 -139
- package/assets/rules/translations.md +0 -96
- package/rt-tools-agent-kit-0.22.0.tgz +0 -0
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: translations-key
|
|
3
|
-
kind: pattern
|
|
4
|
-
rule: translations
|
|
5
|
-
description: Паттерн правила translations. Брать, когда в интерфейсе появляется видимый текст — куда положить ключ, как подставить его в разметку и в класс, чем дозаполнить остальные семь локалей и чем проверить полноту. Не брать для перевода контента объекта — его заполняет бэкенд.
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Ключ перевода
|
|
9
|
-
|
|
10
|
-
Паттерн правила `translations`. Что при этом должно быть верно — закон
|
|
11
|
-
`docs/constitution/application/locales.md`.
|
|
12
|
-
|
|
13
|
-
## Когда брать
|
|
14
|
-
|
|
15
|
-
- В интерфейсе появляется любой текст, который видит человек.
|
|
16
|
-
- Правится подпись, пустое состояние, текст отказа, подпись кнопки подтверждения.
|
|
17
|
-
|
|
18
|
-
## Куда кладётся ключ
|
|
19
|
-
|
|
20
|
-
`libs/common/i18n/src/lib/dictionaries/<локаль>/<раздел>.json`. Разделов четыре, и наборы
|
|
21
|
-
ключей сверяются внутри раздела:
|
|
22
|
-
|
|
23
|
-
| Раздел | Что в нём |
|
|
24
|
-
| ------------- | --------------------------- |
|
|
25
|
-
| `common.json` | общее для сайта и админки |
|
|
26
|
-
| `site.json` | публичный сайт |
|
|
27
|
-
| `admin.json` | админ-панель |
|
|
28
|
-
| `mail.json` | письма и документ-основание |
|
|
29
|
-
|
|
30
|
-
Набор локалей перевода назван в `implementation.md` правила. Ключ заводится во
|
|
31
|
-
всех — пустое значение считается пропуском, а не переводом.
|
|
32
|
-
|
|
33
|
-
## Подстановка
|
|
34
|
-
|
|
35
|
-
В разметке — пайпом:
|
|
36
|
-
|
|
37
|
-
```html
|
|
38
|
-
<h1 rtElem="title">{{ 'bookingsTitle' | transloco }}</h1>
|
|
39
|
-
<<префикс>-table [emptyMessage]="'bookingsEmpty' | transloco" [ariaLabel]="'bookingsTableAria' | transloco">
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
В классе — сигналом:
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
protected readonly title: Signal<string> = translateSignal('promoCodesTitle');
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Дозаполнить и проверить
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
npm run i18n:fill # дозаполняет недостающее в остальных локалях
|
|
52
|
-
npx nx test common-i18n # роняет сборку на недостающем или пустом ключе
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Частые промахи
|
|
56
|
-
|
|
57
|
-
- Текст строкой прямо в шаблоне: он уедет в интерфейс по-английски во всех локалях перевода.
|
|
58
|
-
- Ключ заведён только в `en` и `ru`: тест полноты падает, но замечают это уже в гейте пуша.
|
|
59
|
-
- Пустая строка вместо перевода: на экране она выглядит как задуманная — кнопка без подписи,
|
|
60
|
-
заголовок без текста.
|
|
61
|
-
- Ключ положен не в свой раздел: наборы сверяются внутри раздела, и расхождение вылезет как
|
|
62
|
-
недостача в другом.
|
|
63
|
-
- Свой ключ успеха у панели правки записи: текст успеха принадлежит мутации, и заводится он во
|
|
64
|
-
всех локалях перевода сразу.
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: permissions
|
|
3
|
-
kind: rule
|
|
4
|
-
law: access
|
|
5
|
-
description: Правило под «Закон о доступе». Брать при заведении или правке процедуры Connect, перехватчика входа, гварда маршрута админки и декларации меню. Называет четыре вида доступа, декораторы, сложение пресета с оверрайдами и гейтинг разделов админки. Готовый код — в паттерне permissions-procedure.
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Доступ — как это устроено здесь
|
|
9
|
-
|
|
10
|
-
Правило под закон `docs/constitution/application/access.md`. Закон говорит, что должно быть верно;
|
|
11
|
-
здесь — чем это названо в этом дереве и где лежит. Устройство самих разделов админки —
|
|
12
|
-
`navigation`.
|
|
13
|
-
|
|
14
|
-
## Как это называется здесь
|
|
15
|
-
|
|
16
|
-
| В законе | Здесь |
|
|
17
|
-
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
-
| право | пара «ресурс и действие» строкой: `bookings:manage`, `chat:manage` |
|
|
19
|
-
| объявление доступа | декоратор на классе процедуры: `@RequiresPermission('bookings:manage')`, `@RequiresAuth('причина')`, `@PublicProcedure('причина')`, `@OptionalAuthProcedure('причина')` |
|
|
20
|
-
| пресет | именованный набор прав, выдаваемый пользователю целиком |
|
|
21
|
-
| оверрайд | точечная правка права поверх пресета для одного пользователя |
|
|
22
|
-
| отбивка без входа | `Code.Unauthenticated` |
|
|
23
|
-
| отбивка без права | `Code.PermissionDenied` |
|
|
24
|
-
|
|
25
|
-
## Где это лежит
|
|
26
|
-
|
|
27
|
-
В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
|
|
28
|
-
переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
|
|
29
|
-
же дереве, которое держит код иначе.
|
|
30
|
-
|
|
31
|
-
## Ход
|
|
32
|
-
|
|
33
|
-
Ход проверки доступа: чем закрыта процедура, откуда берутся права вошедшего и чем отличается
|
|
34
|
-
отсутствие входа от отсутствия права.
|
|
35
|
-
|
|
36
|
-
```mermaid
|
|
37
|
-
flowchart TD
|
|
38
|
-
A[Вызов процедуры] --> B{Каким видом доступа она объявлена}
|
|
39
|
-
B -->|Публично| C{Она заводит запись}
|
|
40
|
-
C -->|Да| D[Работает ещё и ограничитель частоты]
|
|
41
|
-
C -->|Нет| E[Работа идёт]
|
|
42
|
-
B -->|Любому вошедшему| F{Вход есть}
|
|
43
|
-
B -->|По праву| F
|
|
44
|
-
F -->|Нет| G[Отбивается как неаутентифицированный]
|
|
45
|
-
F -->|Да| H{Учётная запись ещё существует}
|
|
46
|
-
H -->|Нет| G
|
|
47
|
-
H -->|Да| I[Права читаются при каждом вызове: пресет плюс личные правки поверх]
|
|
48
|
-
I --> J{Право дано}
|
|
49
|
-
J -->|Да| E
|
|
50
|
-
J -->|Нет, либо роль о нём молчит| K[Отбивается как отказ в доступе: молчание — не разрешение]
|
|
51
|
-
D --> E
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Как закон применяется здесь
|
|
55
|
-
|
|
56
|
-
- **Каждая процедура объявляет свой доступ декоратором, и объявление ровно одно.**
|
|
57
|
-
Процедура без объявления или с двумя объявлениями не даёт приложению подняться.
|
|
58
|
-
- **Видов доступа четыре: по праву, любому вошедшему, публично и публично с чтением входа.**
|
|
59
|
-
Последний отдаёт вошедшему больше, чем гостю, — так владелец видит скрытые объекты в общем
|
|
60
|
-
списке.
|
|
61
|
-
- **Права пользователя — это права пресета, поверх которых применены его оверрайды.**
|
|
62
|
-
- **У человека одна роль во владении, и держит это хранилище.** Две принадлежности в одном
|
|
63
|
-
владении пришлось бы складывать, а результат сложения запретов и разрешений по двум строкам
|
|
64
|
-
не прочитать. Права разных владений не складываются вовсе: они записаны у принадлежности, а
|
|
65
|
-
не у учётной записи.
|
|
66
|
-
- **Право, о котором роль ничего не говорит, считается неданным.** Значение, которое не булево,
|
|
67
|
-
отбрасывается при сложении: отсутствующее право и прямо отобранное означают одно и то же, и
|
|
68
|
-
«непусто» правом не считается.
|
|
69
|
-
- **Права вошедшего читаются при каждом вызове, а не берутся из выданного входа.** Выданный
|
|
70
|
-
вход говорит только о том, кто пришёл: подписанное однажды живёт часами и правку прав не
|
|
71
|
-
переживает, поэтому отобранное право открывало бы раздел до конца дня.
|
|
72
|
-
- **Учётная запись, которой больше нет, вызовов, требующих входа, не открывает.** Тем же
|
|
73
|
-
чтением отбивается и человек, потерявший принадлежность в своём владении: работать ему не в
|
|
74
|
-
чем.
|
|
75
|
-
- **Счётчик и рекламные сигналы включает ответ гостя, а не наличие ключа в настройках.**
|
|
76
|
-
Решений два, и хранятся они парой: «разрешил счёт посещений, но не рекламу» — законное
|
|
77
|
-
состояние, а третьим значением перечисления его пришлось бы заводить заново на каждое новое
|
|
78
|
-
разрешение. Всё, что не пара булевых значений, читается как неотвеченный вопрос, то есть как
|
|
79
|
-
отказ: хранилище принимает что угодно, а решать по испорченной записи нельзя. Своя
|
|
80
|
-
статистика к согласию не привязана — она не уходит наружу.
|
|
81
|
-
- **Публичная процедура, заводящая запись, закрыта ещё и ограничителем частоты.** Право её не
|
|
82
|
-
сторожит, и без предела скорость роста таблицы задаёт отправитель, а не владелец. Считается
|
|
83
|
-
по ключу клиента, и ключ у всех таких процедур общий: второй ответ на вопрос «кто это»
|
|
84
|
-
разошёлся бы с первым. Публичная процедура, которая только читает, ограничителя не требует.
|
|
85
|
-
- **Запрос без входа отбивается как неаутентифицированный, а вход без права — как отказ в
|
|
86
|
-
доступе.** Это разные ответы: первый лечится входом, второй — нет.
|
|
87
|
-
- **Право проверяется перехватчиком до тела процедуры.** Обработчик не решает, пускать ли
|
|
88
|
-
вызывающего.
|
|
89
|
-
- **Публичность объявляется с причиной.** Причина — аргумент декоратора, записанный для
|
|
90
|
-
читателя кода; ни в ответ, ни в лог она не уходит.
|
|
91
|
-
- **Пункт меню и адрес раздела закрыты по одной декларации.** Иначе скрытый пункт закрывает
|
|
92
|
-
раздел лишь на вид: адрес открывается по прямой ссылке.
|
|
93
|
-
- **Пока права не получены, админка ничего не прячет.** Пустая шапка после сетевого сбоя
|
|
94
|
-
выглядит как сломанная админка и не оставляет выхода.
|
|
95
|
-
|
|
96
|
-
## Чего из закона здесь нет
|
|
97
|
-
|
|
98
|
-
Право, отобранное посреди сессии, до перевхода не действует: токен живёт со своими правами до
|
|
99
|
-
истечения — это `Q-A-1` в законе. Смены пароля из интерфейса нет вовсе: ни экрана, ни
|
|
100
|
-
процедуры, ни восстановления забытого — `Q-A-2`.
|
|
101
|
-
|
|
102
|
-
## Паттерны
|
|
103
|
-
|
|
104
|
-
- `permissions-procedure` — объявление доступа у процедуры и гейтинг раздела админки.
|
|
105
|
-
|
|
106
|
-
## Ловушки
|
|
107
|
-
|
|
108
|
-
- **Процедура, о которой перехватчик ничего не знает, отбивается как отказ в доступе, а не
|
|
109
|
-
пропускается.**
|
|
110
|
-
- **Гвард стоит на дочерних маршрутах защищённой группы, а не на самой группе:** гвард группы
|
|
111
|
-
отрабатывает один раз за загрузку страницы и переходов между разделами не видит.
|
|
112
|
-
- **Права приходят ответом профиля уже внутри защищённой группы**, поэтому гвард дожидается
|
|
113
|
-
запуска админки. Отказ запроса ожидание не роняет: с неизвестными правами не закрывается
|
|
114
|
-
ничего.
|
|
115
|
-
- Декораторы живут в `util` домена аутентификации, а не рядом с перехватчиком: их ставит
|
|
116
|
-
каждый домен с процедурами, и ребро к объявлениям дешевле ребра к секрету и базе.
|
package/assets/rules/seo.md
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: seo
|
|
3
|
-
kind: rule
|
|
4
|
-
law: search-visibility
|
|
5
|
-
description: Правило под «Закон о видимости в поиске». Брать при любой правке, доходящей до разметки публичного сайта: шаблоны страниц, мета-теги, микроразметка, канонический адрес, языковые версии, карта сайта. Паттерны: seo-page, seo-verify.
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Видимость в поиске — как это устроено здесь
|
|
9
|
-
|
|
10
|
-
Правило под закон `docs/constitution/application/search-visibility.md`. Закон говорит, что должно быть
|
|
11
|
-
верно; здесь — чем это названо в этом коде, где лежит и что из закона у нас не применяется.
|
|
12
|
-
|
|
13
|
-
## Как это называется здесь
|
|
14
|
-
|
|
15
|
-
| В законе | Здесь |
|
|
16
|
-
| ---------------------------- | ------------------------------------------------------------------------ |
|
|
17
|
-
| язык страницы | локаль перевода; их набор — в `implementation.md` рядом |
|
|
18
|
-
| язык по умолчанию | `DEFAULT_LOCALE`, то есть `en`; отдаётся из корня, без префикса в адресе |
|
|
19
|
-
| язык, перевод которого готов | `readyLocales` — поле объекта, а не список локалей сайта |
|
|
20
|
-
| канонический адрес | `${SITE_ORIGIN}${propertyPath(slug, locale)}` |
|
|
21
|
-
| структурированные данные | JSON-LD типа `LodgingBusiness` |
|
|
22
|
-
| прежний адрес страницы | прежний slug объекта |
|
|
23
|
-
|
|
24
|
-
Локаль по умолчанию отдаётся из корня, остальные — с префиксом `/<код>/`. Префикс — часть
|
|
25
|
-
маршрута, а не часть сборки: `<base href>` в разметке всегда `/`.
|
|
26
|
-
|
|
27
|
-
## Где это лежит
|
|
28
|
-
|
|
29
|
-
В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
|
|
30
|
-
переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
|
|
31
|
-
же дереве, которое держит код иначе.
|
|
32
|
-
|
|
33
|
-
## Ход
|
|
34
|
-
|
|
35
|
-
Ход отдачи страницы поисковику: что ставится в разметку, чем связаны локали и что делается с
|
|
36
|
-
прежним адресом.
|
|
37
|
-
|
|
38
|
-
```mermaid
|
|
39
|
-
flowchart TD
|
|
40
|
-
A[Страница отдаётся] --> B[Свои теги помечаются признаком и при повторе переписываются, а не множатся]
|
|
41
|
-
B --> C[Канонический адрес ведёт на локализованный путь этой страницы]
|
|
42
|
-
C --> D[Связи локалей строятся по готовым локалям плюс запасная]
|
|
43
|
-
D --> E{Локаль самой страницы}
|
|
44
|
-
E -->|В список запасных не попадает| F[Иначе она объявлена и своей, и чужой]
|
|
45
|
-
F --> G[Данные для поиска отдаются блоками: отказ одного не уносит остальные]
|
|
46
|
-
G --> H{Адрес страницы менялся}
|
|
47
|
-
H -->|Да| I[С прежнего идёт перенаправление со сроком хранения, покрытое проверкой]
|
|
48
|
-
H -->|Нет| J[Готово]
|
|
49
|
-
I --> J
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Как закон применяется здесь
|
|
53
|
-
|
|
54
|
-
- **Свои теги помечены атрибутом `data-<префикс>-seo`** и при повторном применении переписываются.
|
|
55
|
-
Чужое в `<head>` не трогается: после гидратации там остаются теги от отдачи сервером.
|
|
56
|
-
- **`canonical` ведёт на локализованный путь** `${SITE_ORIGIN}${propertyPath(slug, locale)}`,
|
|
57
|
-
а не на корень и не на адрес локали по умолчанию.
|
|
58
|
-
- **`hreflang` строится по `readyLocales`**, плюс `x-default` на локаль по умолчанию. Список
|
|
59
|
-
локалей сайта для этого не годится: переводы контента заполняет бэк, и готовы они не всегда.
|
|
60
|
-
- **`og:locale:alternate` не включает локаль самой страницы** — иначе она объявлена и
|
|
61
|
-
основной, и альтернативной сразу.
|
|
62
|
-
- **`url` в JSON-LD — канонический адрес страницы объекта**, не корень сайта.
|
|
63
|
-
- **Корень локали отдаёт страницу единственного объекта** с каноническим адресом на `/{slug}`;
|
|
64
|
-
посетителя уводит туда nginx ответом 302.
|
|
65
|
-
- **Перенаправление с прежнего адреса отдаётся с `Cache-Control: max-age`** и покрыто
|
|
66
|
-
`proxy_cache_valid 200 301`. Ключ кэша строится без `$args`, поэтому запросы со строкой
|
|
67
|
-
запроса идут мимо кэша (`proxy_cache_bypass` / `proxy_no_cache $is_args`).
|
|
68
|
-
- **Страница отдаёт блоки JSON-LD каждый своим тегом**, а не одним графом: отказ одного не
|
|
69
|
-
уносит остальные, и в отданной разметке видно, какой из них собрался.
|
|
70
|
-
- **Цена уходит и предложением, и диапазоном** — суммой в валюте хранения и с единицей своего
|
|
71
|
-
периода. Цена по датам, скидки и пересчёт в валюту гостя в разметку не идут.
|
|
72
|
-
- **Сводная оценка появляется от первого отзыва** и считается из оценок самих отзывов. Ноль
|
|
73
|
-
отзывов — поля нет вовсе: ни нуля, ни пустой строки.
|
|
74
|
-
- **Блок вопросов собирается из вопросов записи**, а ноль вопросов блока не даёт.
|
|
75
|
-
- **Крошки ведут от корня локали к странице записи** адресами той же локали, что и открытая
|
|
76
|
-
страница. Без названия сайта крошки не выпускаются вовсе.
|
|
77
|
-
- **Сохранение записи уведомляет поисковика об изменении её адресов.** В уведомление уходят
|
|
78
|
-
страницы записи на готовых локалях по нынешнему и прежним адресам; корни локалей и карта
|
|
79
|
-
сайта — нет. Отказ приёмника сохранение не отменяет и возвращается владельцу исходом.
|
|
80
|
-
- **Приёмников несколько, и каждый получает свой запрос.** Опрашиваются они одновременно, а
|
|
81
|
-
потолок ожидания считается на приёмник: последовательный обход сложил бы все потолки в одно
|
|
82
|
-
ожидание владельца. Переменная окружения перекрывает адреса всех приёмников разом — этим
|
|
83
|
-
отправку и проверяют локальным приёмником.
|
|
84
|
-
- **Исход возвращается перечислением по приёмнику, а владельцу показывается списком.** Приёмник
|
|
85
|
-
в нём назван словом, а не адресом. Строка отвечает за приём запроса, а не за осведомлённость
|
|
86
|
-
поисковика: раздаёт полученное любой приёмник, поэтому отказ одного адреса не значит, что его
|
|
87
|
-
поисковик не узнал. Подписью объявляется только полный отказ.
|
|
88
|
-
- **Удаление записи уведомляет поисковика теми же адресами.** Отдельного «страница исчезла»
|
|
89
|
-
протокол не знает: площадка приходит по адресу и видит ответ сайта сама. Адреса собираются до
|
|
90
|
-
удаления строки — каскад унесёт прежние вместе с записью, — а исход наружу не выходит:
|
|
91
|
-
страницы уже нет, и читать подпись некому. Снятие с публикации идёт через сохранение и
|
|
92
|
-
уведомляет тем же путём.
|
|
93
|
-
- **Переименование адреса и снятие прежнего адреса тоже уведомляют поисковика.**
|
|
94
|
-
Переименование — всеми адресами записи, снятие — одним снятым; готовые локали у снятого
|
|
95
|
-
считаются по текстам записи, которой он принадлежал, и ради них выборка снятия отдаёт не
|
|
96
|
-
только адрес. Исход наружу не выходит ни там, ни там: владелец на странице адресов
|
|
97
|
-
спрашивает про адрес, а не про уведомление.
|
|
98
|
-
- **Правка настроек владельца уведомляет поисковика адресами всех действующих записей.**
|
|
99
|
-
Видимой гостю считается та же правка, которой сбрасывается кэш, — а правка невидимого поля
|
|
100
|
-
уведомления не шлёт. Уходят живые адреса, без прежних, и готовые локали считаются по каждой
|
|
101
|
-
записи отдельно; отправка идёт на владеющую сущность — записи разных сущностей в одну не
|
|
102
|
-
попадают. Работа идёт в фоне за сохранением настроек, поэтому исход остаётся в логах:
|
|
103
|
-
показать его владельцу негде.
|
|
104
|
-
- **Каждая отправка оставляет запись, и заводится она до обращения к приёмникам.** Повод,
|
|
105
|
-
записи, объявленные адреса и исход по каждому приёмнику ложатся рядом; пустой исход означает
|
|
106
|
-
«отправка идёт», а спустя несколько минут — что процесс упал посреди опроса. Пишут её все
|
|
107
|
-
места, откуда уведомление уходит. Отказ записи отправку не отменяет.
|
|
108
|
-
- **Непрошедшие запросы владелец повторяет нажатием, и повтор заводит новую запись.**
|
|
109
|
-
Повторяется последняя отправка записи — у ранней адреса могли устареть, — и запрос уходит
|
|
110
|
-
только тем приёмникам, чей исход не «принял»; у оборванной записи это все её приёмники. Повод
|
|
111
|
-
у новой записи свой, а право строже, чем у чтения: повтор объявляет адреса поисковику.
|
|
112
|
-
Автоповтора по расписанию нет.
|
|
113
|
-
- **Записи отправок держит ночная чистка: сперва предел, потом срок.** Тот же порядок, что у
|
|
114
|
-
ленты происшествий: срок, снятый первым, оставил бы предел меряться по уже почищенной
|
|
115
|
-
таблице.
|
|
116
|
-
|
|
117
|
-
## Чего из закона здесь нет
|
|
118
|
-
|
|
119
|
-
Что отдаёт корень локали — единственную запись или список, — правило не утверждает: это
|
|
120
|
-
состояние дерева, и живёт оно в компаньоне рядом. Развилка от этого не исчезает: там, где
|
|
121
|
-
список появляется, канонический адрес корня пересматривается; это `Q-SV-1` в законе.
|
|
122
|
-
|
|
123
|
-
## Паттерны
|
|
124
|
-
|
|
125
|
-
- `seo-page` — правка разметки страницы: теги, JSON-LD, новый маршрут, новая страница в карте.
|
|
126
|
-
- `seo-verify` — проверка отданной разметки на прод-сборке.
|
|
127
|
-
|
|
128
|
-
## Ловушки
|
|
129
|
-
|
|
130
|
-
- Тег, добавленный мимо `PropertySeoService`, не помечен `data-<префикс>-seo` — он не будет ни
|
|
131
|
-
переписан, ни удалён при следующем применении и переживёт переход между страницами.
|
|
132
|
-
- Новый маршрут сайта без ветки в `app.routes.ts` под каждую локаль существует только в
|
|
133
|
-
локали по умолчанию: `/de/<путь>` отдаст 404 и поисковику, и гостю.
|
|
134
|
-
- Новая страница не попадает в `sitemap.xml` сама — карта строится из объектов, а не из
|
|
135
|
-
маршрутов.
|
|
136
|
-
- Адрес режется по **первому** `?` и только им: `req.url.split('?')` теряет всё после второго
|
|
137
|
-
знака, и перенаправление приходит на страницу без UTM-хвоста — источник заявки считается
|
|
138
|
-
неверно. Разрез живёт в `splitRequestUrl`
|
|
139
|
-
(слой `util` домена страницы объекта) и покрыт спеками; свой не заводить.
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: translations
|
|
3
|
-
kind: rule
|
|
4
|
-
law: locales
|
|
5
|
-
description: Правило под «Закон о локалях и переводах». Брать при заведении любого видимого текста, правке словарей libs/common/i18n, префиксов локалей сайта и перевода контента объекта. Называет локали перевода, словари, производные переводы и начальную валюту локали. Готовый код — в паттерне translations-key.
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Локали и переводы — как это устроено здесь
|
|
9
|
-
|
|
10
|
-
Правило под закон `docs/constitution/application/locales.md`. Закон говорит, что должно быть верно;
|
|
11
|
-
здесь — чем это названо в этом дереве и где лежит. Разметка сайта для поиска — `seo` под
|
|
12
|
-
своим законом.
|
|
13
|
-
|
|
14
|
-
## Как это называется здесь
|
|
15
|
-
|
|
16
|
-
| В законе | Здесь |
|
|
17
|
-
| -------------------- | --------------------------------------------------------------------------------------------------- |
|
|
18
|
-
| локаль сайта | одна из локалей перевода; их набор — в `implementation.md` рядом |
|
|
19
|
-
| локаль по умолчанию | `en` — отдаётся из корня, без префикса в адресе |
|
|
20
|
-
| локаль ввода | `source_locale` в запросе сохранения объекта; берётся из языка админки |
|
|
21
|
-
| словарь | JSON-словари Transloco в `libs/common/i18n/src/lib/dictionaries/<локаль>/`, разложенные по разделам |
|
|
22
|
-
| перевод контента | jsonb по локалям в базе, заполняется бэкендом |
|
|
23
|
-
| подстановка в шаблон | `\| transloco` в разметке, `translateSignal` в классе |
|
|
24
|
-
|
|
25
|
-
## Где это лежит
|
|
26
|
-
|
|
27
|
-
В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
|
|
28
|
-
переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
|
|
29
|
-
же дереве, которое держит код иначе.
|
|
30
|
-
|
|
31
|
-
## Ход
|
|
32
|
-
|
|
33
|
-
Ход появления видимого текста: откуда он берётся, что делается с непришедшим переводом и чем
|
|
34
|
-
держится полнота словарей.
|
|
35
|
-
|
|
36
|
-
```mermaid
|
|
37
|
-
flowchart TD
|
|
38
|
-
A[Появился видимый человеку текст] --> B{Чей он}
|
|
39
|
-
B -->|Интерфейса| C[Заводится ключом во всех локалях перевода сразу]
|
|
40
|
-
B -->|Содержимого| D[Владелец пишет на своём языке, остальные локали — производные]
|
|
41
|
-
C --> E{Перевод пуст}
|
|
42
|
-
E -->|Да| F[Считается пропуском: подставляется запасной словарь, а прогон краснеет]
|
|
43
|
-
E -->|Нет| G[Готово]
|
|
44
|
-
D --> H{Перевод пришёл}
|
|
45
|
-
H -->|Да| I[Записывается]
|
|
46
|
-
H -->|Нет| J[Прежнее значение сохраняется, а само сохранение не срывается]
|
|
47
|
-
I --> G
|
|
48
|
-
J --> G
|
|
49
|
-
F --> G
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Как закон применяется здесь
|
|
53
|
-
|
|
54
|
-
- **Локаль по умолчанию отдаётся из корня, остальные семь — из-под префикса пути.**
|
|
55
|
-
- **Видимый человеку текст берётся из словаря и заводится во всех локалях перевода.** Ключ,
|
|
56
|
-
потерянный в одной локали, гость видит на кнопке как есть.
|
|
57
|
-
- **Пустой перевод считается пропуском, а не переводом.** Запасной словарь подставляется
|
|
58
|
-
только на отсутствующий ключ, а пустую строку отдаёт как есть.
|
|
59
|
-
- **Переводы контента производные: владелец пишет на своём языке, остальные семь локалей
|
|
60
|
-
заполняет бэкенд при сохранении.** Табов локалей в формах админки нет — правка вручную всё
|
|
61
|
-
равно была бы перезаписана.
|
|
62
|
-
- **Отказ перевода сохранение не срывает.** Текст на локали ввода записывается, прежние
|
|
63
|
-
переводы остаются, и владелец видит предупреждение, а не сообщение об успехе.
|
|
64
|
-
- **Локаль, для которой перевода не пришло, сохраняет прежнее значение.**
|
|
65
|
-
- **Язык админки выбирает владелец, и выбор живёт в его профиле.** Он же уходит в локаль
|
|
66
|
-
ввода при сохранении объекта.
|
|
67
|
-
- **У локали есть начальная валюта показа.** Дальше валюту выбирает гость, и его выбор
|
|
68
|
-
сильнее умолчания локали.
|
|
69
|
-
|
|
70
|
-
## Чего из закона здесь нет
|
|
71
|
-
|
|
72
|
-
Страница не показывает, что перевод устарел: владелец правку сохранил, провайдер отказал —
|
|
73
|
-
и гость по-прежнему видит старый текст. Это `Q-L-1` в законе.
|
|
74
|
-
|
|
75
|
-
## Паттерны
|
|
76
|
-
|
|
77
|
-
- `translations-key` — заведение ключа во всех локалях перевода и подстановка в разметку.
|
|
78
|
-
- `translations-content` — перевод содержимого записи: производный перевод, запись локали
|
|
79
|
-
руками, отказ провайдера, сброс кэша отданных страниц.
|
|
80
|
-
|
|
81
|
-
## Ловушки
|
|
82
|
-
|
|
83
|
-
- **Полноту словарей держит не рантайм, а тест.** `nx test common-i18n` роняет сборку на
|
|
84
|
-
недостающем или пустом ключе; дозаполнить недостающее — `npm run i18n:fill`.
|
|
85
|
-
- **Наборы ключей сверяются внутри раздела**, а не по всему словарю сразу: словари разложены
|
|
86
|
-
на общий, сайт, письма и админку.
|
|
87
|
-
- **Перевод контента идёт до транзакции сохранения:** страница объекта не должна оказаться
|
|
88
|
-
наполовину переведённой. Кэш сбрасывается после записи и один раз.
|
|
89
|
-
- **Без ключа переводов сохранение проходит,** но переводы остаются прежними, и владелец видит
|
|
90
|
-
предупреждение об этом на своём экране. Ключ лежит секретом владения в хранилище, а не
|
|
91
|
-
переменной окружения, и заводит его владелец экраном интеграций: одноимённая переменная в
|
|
92
|
-
составе прода приложением не читается. Состояния ключа — паттерн `git-workflow-secrets`.
|
|
93
|
-
- **Ветки локалей надеты не на все маршруты сайта, и новый раздел в них не попадает** —
|
|
94
|
-
раскладка, её ловушка и готовый код лежат в правиле `seo` и паттерне `seo-page`.
|
|
95
|
-
- Новый маршрут сайта без ветки под каждую локаль существует только в локали по умолчанию:
|
|
96
|
-
путь под префиксом другой локали отдаст 404 и поисковику, и гостю.
|
|
Binary file
|