Compare commits
244 Commits
Author | SHA1 | Date | |
---|---|---|---|
d96ff13a67 | |||
2c1351ce47 | |||
96cd56ec77 | |||
e5b2096d65 | |||
3aa140335f | |||
4cafaa2492 | |||
9c633a7521 | |||
e27845ba91 | |||
2a8708a45b | |||
6874fa4c24 | |||
ba531a4927 | |||
20175b57cf | |||
ad275e4c34 | |||
060b9fe0de | |||
17b24d14ed | |||
2d278b0680 | |||
fb5975e4f1 | |||
24fccaf513 | |||
293953aa1b | |||
1049e312f9 | |||
a2db250600 | |||
cf7fe8c337 | |||
f5350097bf | |||
1cb5dd461b | |||
845599a5e8 | |||
0cc02c292f | |||
1919702326 | |||
0c0052e1cd | |||
78622770ec | |||
7b86727394 | |||
0965f8648e | |||
98974b4367 | |||
597bcadd9e | |||
4d9aabcb91 | |||
1606c2884d | |||
12f69b593f | |||
1ca45f90d0 | |||
a91a5616f9 | |||
c525e09368 | |||
f5bba4a6a0 | |||
77a37fb573 | |||
6b24c5878c | |||
f4414e1249 | |||
b72971f4ce | |||
b9af4e6804 | |||
2fd1d42d1e | |||
3cfc7d7fa9 | |||
b5d9055fcf | |||
63d644d95f | |||
e16192b416 | |||
505e018448 | |||
5ced0e2809 | |||
0e1d919f7e | |||
a009db998e | |||
d6c6bd933b | |||
859cc03f35 | |||
1a3b8ae3b8 | |||
863a08abf3 | |||
fd9c6afa5e | |||
8f3797407b | |||
7eedb23285 | |||
e4a2c95dd8 | |||
9429228b71 | |||
aafbc60f12 | |||
7170611791 | |||
59e57f3dd5 | |||
fd0d25b081 | |||
fa529c911a | |||
dc997b7ef4 | |||
0168d32f96 | |||
d6fc0c779c | |||
08be36edfa | |||
990d287953 | |||
a629d4ab45 | |||
78b48af886 | |||
72d267853c | |||
66a218c2ec | |||
d4f3ec2245 | |||
7bdc19bf4b | |||
103ef788fb | |||
b6f6d1f3cc | |||
ef3d2e4e04 | |||
8ec8b91ead | |||
819a1d473d | |||
c930337255 | |||
57bb4a9d96 | |||
1776341242 | |||
983eec6941 | |||
86d390ee1a | |||
91703409d9 | |||
3322d0e4a5 | |||
d09038fde2 | |||
71966deaac | |||
12e83c9468 | |||
fe27357dbb | |||
b93003e76d | |||
d6c0e9f783 | |||
18a1baae59 | |||
2330788995 | |||
40c146022a | |||
9844e7554f | |||
b064b8cbe6 | |||
997941fbf7 | |||
8d5e080bd6 | |||
1c1ca25287 | |||
5195b3bc1e | |||
3ff56cfea7 | |||
adc6d69201 | |||
438ca4595f | |||
740d9b7af5 | |||
919a6947bc | |||
85fdaa2f22 | |||
6ade3d6375 | |||
73e9ebbe40 | |||
ad78221025 | |||
714fec0bf3 | |||
de312c60b1 | |||
1d07fd7675 | |||
307650aaea | |||
6eccd313b6 | |||
b8751d67db | |||
25d9d3dc26 | |||
68e6c9faaf | |||
f3fb360ce0 | |||
d3631dd10c | |||
891c91aa20 | |||
880018e926 | |||
06ab2ab82e | |||
aafcfb62f2 | |||
a69bee8726 | |||
240208793d | |||
e7a320f8f8 | |||
f76438dd82 | |||
d6dbb42dea | |||
2e076aa058 | |||
b59447b840 | |||
702352bea2 | |||
98f647fadf | |||
85d3a011d8 | |||
26b0b55a7d | |||
50d0e74c03 | |||
5a7fb7c303 | |||
e9f5a79d69 | |||
1378dbebee | |||
7daa818996 | |||
c8e219361b | |||
b74323a48e | |||
0cfe240590 | |||
533105d63a | |||
62e4af2c78 | |||
934c07f365 | |||
624409434a | |||
546c74aa28 | |||
2ee12abc43 | |||
b9717e649f | |||
09c7b6ac03 | |||
079da86cf1 | |||
57502a57af | |||
6738f6f6cf | |||
21763db561 | |||
24e07c3e00 | |||
8820262b0f | |||
2d69a1f946 | |||
2a5a4c16ea | |||
52ba14cd8f | |||
6222850453 | |||
ec2b085855 | |||
305d5b7328 | |||
deb4ddaed2 | |||
379f052e6e | |||
2f592ab38c | |||
43c412405a | |||
7f6ab1d391 | |||
e5e39d036d | |||
6a93c6e894 | |||
069262c83d | |||
69f017df36 | |||
63ccb0609e | |||
292881532a | |||
d35eac9661 | |||
f92488b80d | |||
46c482d098 | |||
1bd05d22c3 | |||
9e51ef90c0 | |||
b6fadad6cc | |||
193f66c843 | |||
c3459106d6 | |||
931ce161d7 | |||
83ebf93f2a | |||
84b9909931 | |||
8d91ea6307 | |||
fa99d8dd9a | |||
67243ecfc6 | |||
6cfefd7fbd | |||
f599df2bb7 | |||
57a0d9f10f | |||
34902b54e4 | |||
5db564f481 | |||
0924a42341 | |||
be295ff328 | |||
692ef67657 | |||
1f1c050467 | |||
8ff05917f2 | |||
a09157fe98 | |||
e61fba3ffd | |||
58ad8c1568 | |||
cd145b42d6 | |||
a2ddadf88b | |||
f70625bf3f | |||
06bc126584 | |||
63a0a30d44 | |||
06c1ef7eea | |||
9fc12ee74d | |||
b6bc851e60 | |||
217b2666a1 | |||
d122d17ff5 | |||
fef6883885 | |||
afe4fd4272 | |||
df00825abb | |||
79eb207b71 | |||
b884d157de | |||
6e2b9071a6 | |||
9d4dc5500d | |||
867b9b6965 | |||
a57700f978 | |||
f4f8145fca | |||
1e500c9344 | |||
de3dce2598 | |||
1e749b6df8 | |||
95e04a282f | |||
545e73d2c8 | |||
79cca43ad2 | |||
278f83a0f3 | |||
8024631dcc | |||
690596a347 | |||
5b361df960 | |||
3482951a61 | |||
2a666f4823 | |||
6098e94f0e | |||
6d0f43da96 | |||
9d8eed255d | |||
dda2d59690 | |||
8b28b10aea | |||
da5efb67e0 |
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
src/lib/i18n/generated_messages/* linguist-documentation
|
||||
src/bin/install-builtin-keycloak-themes.ts -linguist-detectable
|
||||
src/bin/build-keycloak-theme/index.ts -linguist-detectable
|
172
.github/workflows/ci.yaml
vendored
172
.github/workflows/ci.yaml
vendored
@ -2,75 +2,135 @@ name: ci
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- main
|
||||
|
||||
jobs:
|
||||
|
||||
test_node:
|
||||
runs-on: ${{ matrix.os }}
|
||||
test:
|
||||
runs-on: macos-10.15
|
||||
strategy:
|
||||
matrix:
|
||||
node: [ '14', '13', '12' ]
|
||||
os: [ ubuntu-latest ]
|
||||
name: Test with Node v${{ matrix.node }} on ${{ matrix.os }}
|
||||
name: Test with Node v${{ matrix.node }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- name: Tell if project is using npm or yarn
|
||||
id: step1
|
||||
uses: garronej/github_actions_toolkit@v2.2
|
||||
with:
|
||||
action_name: tell_if_project_uses_npm_or_yarn
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/setup-node@v2.1.3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm run test
|
||||
|
||||
|
||||
trigger_publish:
|
||||
name: Trigger publish.yaml workflow if package.json version updated ( and secrets.PAT is set ).
|
||||
- if: steps.step1.outputs.npm_or_yarn == 'yarn'
|
||||
run: |
|
||||
yarn install --frozen-lockfile
|
||||
yarn build
|
||||
yarn test
|
||||
- if: steps.step1.outputs.npm_or_yarn == 'npm'
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
npm test
|
||||
check_if_version_upgraded:
|
||||
name: Check if version upgrade
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PAT: ${{secrets.PAT}}
|
||||
if: github.event_name == 'push' && github.event.head_commit.author.name != 'ts_ci'
|
||||
needs: test_node
|
||||
needs: test
|
||||
outputs:
|
||||
from_version: ${{ steps.step1.outputs.from_version }}
|
||||
to_version: ${{ steps.step1.outputs.to_version }}
|
||||
is_upgraded_version: ${{steps.step1.outputs.is_upgraded_version }}
|
||||
steps:
|
||||
|
||||
- name: Get version on latest
|
||||
id: v_latest
|
||||
uses: garronej/github_actions_toolkit@v1.9
|
||||
- uses: garronej/github_actions_toolkit@v2.2
|
||||
id: step1
|
||||
with:
|
||||
action_name: get_package_json_version
|
||||
owner: ${{github.repository_owner}}
|
||||
repo: ${{github.event.repository.name}}
|
||||
branch: latest
|
||||
compare_to_version: '0.0.0'
|
||||
action_name: is_package_json_version_upgraded
|
||||
|
||||
- name: Get version on develop
|
||||
id: v_develop
|
||||
uses: garronej/github_actions_toolkit@v1.9
|
||||
with:
|
||||
action_name: get_package_json_version
|
||||
owner: ${{github.repository_owner}}
|
||||
repo: ${{github.event.repository.name}}
|
||||
branch: ${{ github.sha }}
|
||||
compare_to_version: ${{steps.v_latest.outputs.version || '0.0.0'}}
|
||||
|
||||
- name: 'Trigger the ''publish'' workflow'
|
||||
if: ${{ !!env.PAT && steps.v_develop.outputs.compare_result == '1' }}
|
||||
uses: garronej/github_actions_toolkit@v1.9
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||
update_changelog:
|
||||
runs-on: ubuntu-latest
|
||||
needs: check_if_version_upgraded
|
||||
if: needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true'
|
||||
steps:
|
||||
- uses: garronej/github_actions_toolkit@v2.2
|
||||
with:
|
||||
action_name: dispatch_event
|
||||
owner: ${{github.repository_owner}}
|
||||
repo: ${{github.event.repository.name}}
|
||||
event_type: publish
|
||||
client_payload_json: |
|
||||
${{
|
||||
format(
|
||||
'{{"from_version":"{0}","to_version":"{1}","repo":"{2}"}}',
|
||||
steps.v_latest.outputs.version,
|
||||
steps.v_develop.outputs.version,
|
||||
github.event.repository.name
|
||||
)
|
||||
}}
|
||||
action_name: update_changelog
|
||||
branch: ${{ github.ref }}
|
||||
commit_author_email: ts_ci@github.com
|
||||
|
||||
create_github_release:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- update_changelog
|
||||
- check_if_version_upgraded
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
- name: Build GitHub release body
|
||||
id: step1
|
||||
run: |
|
||||
if [ "$FROM_VERSION" = "0.0.0" ]; then
|
||||
echo "::set-output name=body::🚀"
|
||||
else
|
||||
echo "::set-output name=body::📋 [CHANGELOG](https://github.com/$GITHUB_REPOSITORY/blob/v$TO_VERSION/CHANGELOG.md)"
|
||||
fi
|
||||
env:
|
||||
FROM_VERSION: ${{ needs.check_if_version_upgraded.outputs.from_version }}
|
||||
TO_VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}
|
||||
- uses: garronej/action-gh-release@v0.2.0
|
||||
with:
|
||||
name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
|
||||
tag_name: v${{ needs.check_if_version_upgraded.outputs.to_version }}
|
||||
target_commitish: ${{ github.ref }}
|
||||
body: ${{ steps.step1.outputs.body }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
publish_on_npm:
|
||||
runs-on: macos-10.15
|
||||
needs:
|
||||
- update_changelog
|
||||
- check_if_version_upgraded
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
- uses: actions/setup-node@v2.1.3
|
||||
with:
|
||||
node-version: '15'
|
||||
- run: |
|
||||
PACKAGE_MANAGER=npm
|
||||
if [ -f "./yarn.lock" ]; then
|
||||
PACKAGE_MANAGER=yarn
|
||||
fi
|
||||
if [ "$PACKAGE_MANAGER" = "yarn" ]; then
|
||||
yarn install --frozen-lockfile
|
||||
else
|
||||
npm ci
|
||||
fi
|
||||
$PACKAGE_MANAGER run build
|
||||
- run: npx -y -p denoify@0.6.5 denoify_enable_short_npm_import_path
|
||||
env:
|
||||
DRY_RUN: "0"
|
||||
- name: Publishing on NPM
|
||||
run: |
|
||||
if [ "$(npm show . version)" = "$VERSION" ]; then
|
||||
echo "This version is already published"
|
||||
exit 0
|
||||
fi
|
||||
if [ "$NPM_TOKEN" = "" ]; then
|
||||
echo "Can't publish on NPM, You must first create a secret called NPM_TOKEN that contains your NPM auth token. https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets"
|
||||
false
|
||||
fi
|
||||
echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > .npmrc
|
||||
npm publish
|
||||
rm .npmrc
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}
|
106
.github/workflows/publish.yaml
vendored
106
.github/workflows/publish.yaml
vendored
@ -1,106 +0,0 @@
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: publish
|
||||
|
||||
jobs:
|
||||
update_changelog_and_sync_package_lock_version:
|
||||
name: Update CHANGELOG.md and make sure package.json and package-lock.json versions matches.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Synchronize package.json and package-lock.json version if needed.
|
||||
uses: garronej/github_actions_toolkit@v1.9
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||
with:
|
||||
action_name: sync_package_and_package_lock_version
|
||||
owner: ${{github.repository_owner}}
|
||||
repo: ${{github.event.client_payload.repo}}
|
||||
branch: develop
|
||||
commit_author_email: ts_ci@github.com
|
||||
- name: Update CHANGELOG.md
|
||||
if: ${{ !!github.event.client_payload.from_version }}
|
||||
uses: garronej/github_actions_toolkit@v1.9
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||
with:
|
||||
action_name: update_changelog
|
||||
owner: ${{github.repository_owner}}
|
||||
repo: ${{github.event.client_payload.repo}}
|
||||
branch_behind: latest
|
||||
branch_ahead: develop
|
||||
commit_author_email: ts_ci@github.com
|
||||
exclude_commit_from_author_names_json: '["ts_ci"]'
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
needs: update_changelog_and_sync_package_lock_version
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: develop
|
||||
- name: Remove .github directory, useless on 'latest' branch
|
||||
run: rm -r .github
|
||||
- name: Remove branch 'latest'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
git branch -d latest || true
|
||||
git push origin :latest
|
||||
- name: Create the new 'latest' branch
|
||||
run: |
|
||||
git branch latest
|
||||
git checkout latest
|
||||
- uses: actions/setup-node@v1
|
||||
- run: npm ci
|
||||
- run: npm run enable_short_import_path
|
||||
env:
|
||||
DRY_RUN: "0"
|
||||
- name: (DEBUG) Show how the files have been moved to enable short import
|
||||
run: ls -lR
|
||||
- name: Publishing on NPM
|
||||
run: |
|
||||
if [ "$(npm show . version)" = "$VERSION" ]; then
|
||||
echo "This version is already published"
|
||||
exit 0
|
||||
fi
|
||||
if [ "$NPM_TOKEN" = "" ]; then
|
||||
echo "Can't publish on NPM, You must first create a secret called NPM_TOKEN that contains your NPM auth token. https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets"
|
||||
false
|
||||
fi
|
||||
echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > .npmrc
|
||||
npm publish
|
||||
rm .npmrc
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
VERSION: ${{ github.event.client_payload.to_version }}
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --local user.email "ts_ci@github.com"
|
||||
git config --local user.name "ts_ci"
|
||||
git add -A
|
||||
git commit -am "Enabling shorter import paths [automatic]"
|
||||
- run: git push origin latest
|
||||
- name: Release body
|
||||
id: id_rb
|
||||
run: |
|
||||
if [ "$FROM_VERSION" = "" ]; then
|
||||
echo "::set-output name=body::🚀"
|
||||
else
|
||||
echo "::set-output name=body::📋 [CHANGELOG](https://github.com/$OWNER/$REPO/blob/$REF/CHANGELOG.md)"
|
||||
fi
|
||||
env:
|
||||
FROM_VERSION: ${{ github.event.client_payload.from_version }}
|
||||
OWNER: ${{github.repository_owner}}
|
||||
REPO: ${{github.event.client_payload.repo}}
|
||||
REF: v${{github.event.client_payload.to_version}}
|
||||
- name: Create Release
|
||||
uses: garronej/create-release@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||
with:
|
||||
tag_name: v${{ github.event.client_payload.to_version }}
|
||||
release_name: Release v${{ github.event.client_payload.to_version }}
|
||||
branch: latest
|
||||
draft: false
|
||||
prerelease: false
|
||||
body: ${{ steps.id_rb.outputs.body }}
|
271
CHANGELOG.md
271
CHANGELOG.md
@ -1,3 +1,274 @@
|
||||
### **0.3.10** (2021-03-26)
|
||||
|
||||
- Handle <style> tag, improve documentation
|
||||
|
||||
### **0.3.9** (2021-03-25)
|
||||
|
||||
- Update readme
|
||||
- Document --external-assets
|
||||
- Update README.md
|
||||
- Update README.md
|
||||
- Update README.md
|
||||
|
||||
### **0.3.8** (2021-03-22)
|
||||
|
||||
- Make standalone mode the default
|
||||
|
||||
### **0.3.7** (2021-03-22)
|
||||
|
||||
- (test) external asset mode by default
|
||||
|
||||
### **0.3.6** (2021-03-22)
|
||||
|
||||
- Fix previous release
|
||||
|
||||
### **0.3.5** (2021-03-22)
|
||||
|
||||
- support homepage with urlPath
|
||||
|
||||
### **0.3.4** (2021-03-22)
|
||||
|
||||
- Bugfix: Import assets from CSS
|
||||
|
||||
### **0.3.3** (2021-03-22)
|
||||
|
||||
- Fix submit not receving correct text
|
||||
|
||||
### **0.3.2** (2021-03-21)
|
||||
|
||||
- Fix broken previous release
|
||||
|
||||
### **0.3.1** (2021-03-21)
|
||||
|
||||
- kcHeaderClass can be updated after initial mount
|
||||
|
||||
## **0.3.0** (2021-03-20)
|
||||
|
||||
- Bump version
|
||||
- Feat: Cary over states using URL search params
|
||||
- Bugfix: with kcHtmlClass
|
||||
|
||||
### **0.2.10** (2021-03-19)
|
||||
|
||||
- Remove dependency to denoify
|
||||
|
||||
### **0.2.9** (2021-03-19)
|
||||
|
||||
- Update deps and CI workflow
|
||||
|
||||
### **0.2.8** (2021-03-19)
|
||||
|
||||
- Bugfix: keycloak_build that grow and grow in size
|
||||
- Add disclaimer about maitainment strategy
|
||||
- Add a note for tested version support
|
||||
|
||||
### **0.2.7** (2021-03-13)
|
||||
|
||||
- Bump version
|
||||
- Update README.md
|
||||
- Update README.md
|
||||
|
||||
### **0.2.6** (2021-03-10)
|
||||
|
||||
- Fix generated gitignore
|
||||
|
||||
### **0.2.5** (2021-03-10)
|
||||
|
||||
- Fix generated .gitignore
|
||||
|
||||
### **0.2.4** (2021-03-10)
|
||||
|
||||
- Update README.md
|
||||
|
||||
### **0.2.3** (2021-03-09)
|
||||
|
||||
- fix gitignore generation
|
||||
|
||||
### **0.2.2** (2021-03-08)
|
||||
|
||||
- Add table of content
|
||||
- Update README.md
|
||||
- Update README.md
|
||||
|
||||
## **0.2.1** (2021-03-08)
|
||||
|
||||
- Update ci.yaml
|
||||
- Update readme
|
||||
- Update readme
|
||||
- update deps
|
||||
- Update readme
|
||||
- Add all mocks for testing
|
||||
- many small fixes
|
||||
|
||||
### **0.1.6** (2021-03-07)
|
||||
|
||||
- Fix Turkish
|
||||
|
||||
### **0.1.5** (2021-03-07)
|
||||
|
||||
- Fix getKcLanguageLabel
|
||||
|
||||
### **0.1.4** (2021-03-07)
|
||||
|
||||
|
||||
|
||||
### **0.1.3** (2021-03-07)
|
||||
|
||||
- Implement LoginVerifyEmail
|
||||
- Implement login-reset-password.ftl
|
||||
|
||||
### **0.1.2** (2021-03-07)
|
||||
|
||||
- Fix build
|
||||
- Fix build
|
||||
|
||||
### **0.1.1** (2021-03-06)
|
||||
|
||||
- Implement Error page
|
||||
- rename pageBasename by pageId
|
||||
- Implement reactive programing for language switching
|
||||
- Add Info page, refactor
|
||||
|
||||
## **0.1.0** (2021-03-05)
|
||||
|
||||
- Rename keycloakify
|
||||
|
||||
### **0.0.33** (2021-03-05)
|
||||
|
||||
- Fix syncronization with non react pages
|
||||
|
||||
### **0.0.32** (2021-03-05)
|
||||
|
||||
- bump version
|
||||
- Add log to tell when we are using react
|
||||
- Fix missing parentesis
|
||||
|
||||
### **0.0.31** (2021-03-05)
|
||||
|
||||
- Fix typo
|
||||
- Fix register page 500
|
||||
|
||||
### **0.0.30** (2021-03-05)
|
||||
|
||||
- Edit language statistique
|
||||
|
||||
### **0.0.30** (2021-03-05)
|
||||
|
||||
- avoid escaping urls
|
||||
- Use default value instead of value
|
||||
- Fix double single quote problem in messages
|
||||
- Fix typo
|
||||
- Fix non editable username
|
||||
- Fix some bugs
|
||||
- Fix Object.deepAssign
|
||||
- Make the dongle to download smaller
|
||||
- Split kcContext among pages
|
||||
- Implement register
|
||||
|
||||
### **0.0.29** (2021-03-04)
|
||||
|
||||
- Fix build
|
||||
- Fix i18n
|
||||
- Login appear to be working now
|
||||
- closer but not there yet
|
||||
|
||||
### **0.0.28** (2021-03-03)
|
||||
|
||||
- fix build
|
||||
- There is no reason not to let use translations outside of keycloak
|
||||
|
||||
### **0.0.27** (2021-03-02)
|
||||
|
||||
- Implement entrypoint
|
||||
|
||||
### **0.0.26** (2021-03-02)
|
||||
|
||||
- Login page implemented
|
||||
- Implement login
|
||||
- remove unesseary log
|
||||
|
||||
### **0.0.25** (2021-03-02)
|
||||
|
||||
- Fix build and reduce size
|
||||
- Implement the template
|
||||
|
||||
### **0.0.24** (2021-03-01)
|
||||
|
||||
- update
|
||||
- update
|
||||
- update
|
||||
|
||||
### **0.0.23** (2021-03-01)
|
||||
|
||||
- update
|
||||
|
||||
### **0.0.23** (2021-03-01)
|
||||
|
||||
- update
|
||||
- update
|
||||
|
||||
### **0.0.23** (2021-03-01)
|
||||
|
||||
- update
|
||||
- update
|
||||
|
||||
### **0.0.23** (2021-03-01)
|
||||
|
||||
- update
|
||||
- Handle formatting in translation function
|
||||
|
||||
### **0.0.22** (2021-02-28)
|
||||
|
||||
- Split page messages
|
||||
|
||||
### **0.0.21** (2021-02-28)
|
||||
|
||||
- Restore yarn file
|
||||
- Multiple fixes
|
||||
- Update deps
|
||||
- Update deps
|
||||
- includes translations
|
||||
- Update README.md
|
||||
- improve docs
|
||||
- update
|
||||
- Update README.md
|
||||
- update
|
||||
- update
|
||||
- update
|
||||
- update
|
||||
|
||||
### **0.0.20** (2021-02-27)
|
||||
|
||||
- update
|
||||
- update
|
||||
|
||||
### **0.0.19** (2021-02-27)
|
||||
|
||||
- update
|
||||
- update
|
||||
|
||||
### **0.0.18** (2021-02-23)
|
||||
|
||||
- Bump version number
|
||||
- Moving on with implementation of the lib
|
||||
- Update readme
|
||||
- Readme eddit
|
||||
- Fixing video link
|
||||
|
||||
### **0.0.16** (2021-02-23)
|
||||
|
||||
- Bump version
|
||||
- Give test container credentials
|
||||
|
||||
### **0.0.14** (2021-02-23)
|
||||
|
||||
- Bump version number
|
||||
- enable the docker container to be run from the root of the react project
|
||||
|
||||
### **0.0.13** (2021-02-23)
|
||||
|
||||
- bump version
|
||||
|
||||
### **0.0.12** (2021-02-23)
|
||||
|
||||
- update readme
|
||||
|
325
README.md
325
README.md
@ -1,96 +1,321 @@
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/6702424/80216211-00ef5280-863e-11ea-81de-59f3a3d4b8e4.png">
|
||||
<img src="https://user-images.githubusercontent.com/6702424/109387840-eba11f80-7903-11eb-9050-db1dad883f78.png">
|
||||
</p>
|
||||
<p align="center">
|
||||
<i>🔏 Keycloak theme generator for Reacts app💅</i>
|
||||
<i>🔏 Customize key cloak's pages as if they were part of your App 🔏</i>
|
||||
<br>
|
||||
<br>
|
||||
<img src="https://github.com/garronej/keycloak-react-theming/workflows/ci/badge.svg?branch=develop">
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/keycloak-react-theming">
|
||||
<img src="https://img.shields.io/npm/dw/keycloak-react-theming">
|
||||
<img src="https://img.shields.io/npm/l/keycloak-react-theming">
|
||||
<img src="https://github.com/garronej/keycloakify/workflows/ci/badge.svg?branch=develop">
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/keycloakify">
|
||||
<img src="https://img.shields.io/npm/dw/keycloakify">
|
||||
<img src="https://img.shields.io/npm/l/keycloakify">
|
||||
</p>
|
||||
|
||||
# MOTIVATION
|
||||
<p align="center">
|
||||
<i>Ultimately this build tool Generates a Keycloak theme</i>
|
||||
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
||||
</p>
|
||||
|
||||
# Motivations
|
||||
|
||||
The problem:
|
||||
|
||||

|
||||
|
||||
<p align="center">
|
||||
<i>Without keycloakify:</i><br>
|
||||
<img src="https://user-images.githubusercontent.com/6702424/108838381-dbbbcf80-75d3-11eb-8ae8-db41563ef9db.gif">
|
||||
</p>
|
||||
When we redirected to Keycloak the user suffers from a harsh context switch.
|
||||
The language is set back to default and the theme is different.
|
||||
|
||||
Keycloak does offer a way to customize theses pages but it requires a lot of raw HTML/CSS hacking
|
||||
to reproduce the look and feel of a specific app. Not mentioning the maintenance cost of such an endeavour.
|
||||
|
||||
Wouldn't it be great if we could just design the login and register pages as if they where part of our app while
|
||||
still letting Keycloak handle the heavy lifting of actually authenticating the users?
|
||||
Wouldn't it be great if we could just design the login and register pages as if they where part of our app?
|
||||
Here is `yarn add keycloakify` for you 🍸
|
||||
|
||||
Here is `yarn add keycloak-react-theming` for you 🍸
|
||||
<p align="center">
|
||||
<i>With keycloakify:</i><br>
|
||||
<img src="https://github.com/InseeFrLab/keycloakify/releases/download/v0.3.8/keycloakify_after.gif">
|
||||
</p>
|
||||
|
||||

|
||||

|
||||

|
||||
**TL;DR**: [Here](https://github.com/garronej/keycloakify-demo-app) is a Hello World React project with Keycloakify set up.
|
||||
|
||||
- [Motivations](#motivations)
|
||||
- [How to use](#how-to-use)
|
||||
- [Setting up the build tool](#setting-up-the-build-tool)
|
||||
- [Specify from where the resources should be downloaded.](#specify-from-where-the-resources-should-be-downloaded)
|
||||
- [Developing your login and register pages in your React app](#developing-your-login-and-register-pages-in-your-react-app)
|
||||
- [Just changing the look](#just-changing-the-look)
|
||||
- [Changing the look **and** feel](#changing-the-look-and-feel)
|
||||
- [Hot reload](#hot-reload)
|
||||
- [GitHub Actions](#github-actions)
|
||||
- [Requirements](#requirements)
|
||||
- [Limitations](#limitations)
|
||||
- [`process.env.PUBLIC_URL` not supported.](#processenvpublic_url-not-supported)
|
||||
- [`@font-face` importing fonts from the `src/` dir](#font-face-importing-fonts-from-thesrc-dir)
|
||||
- [Example of setup that **won't** work](#example-of-setup-that-wont-work)
|
||||
- [Workarounds](#workarounds)
|
||||
- [Implement context persistence (optional)](#implement-context-persistence-optional)
|
||||
- [API Reference](#api-reference)
|
||||
- [The build tool](#the-build-tool)
|
||||
|
||||
# How to use
|
||||
|
||||
## Setting up the build tool
|
||||
|
||||
Add `keycloak-react-theming` to the dev dependencies of your project `npm install --save-dev keycloak-react-theming` or `yarn add --dev keycloak-react-theming`
|
||||
then configure your `package.json` build's script to build the keycloak's theme by adding `&& build-keycloak-theme`.
|
||||
|
||||
Typically you will get:
|
||||
|
||||
`package.json`:
|
||||
[`package.json`](https://github.com/garronej/keycloakify-demo-app/blob/main/package.json)
|
||||
```json
|
||||
"devDependencies": {
|
||||
"keycloak-react-theming": "^0.0.10",
|
||||
[...]
|
||||
},
|
||||
"scripts": {
|
||||
"build": "react-scripts build && build-keycloak-theme",
|
||||
[...]
|
||||
},
|
||||
"homepage": "https://URL.OF/YOUR-APP"
|
||||
"dependencies": {
|
||||
"keycloakify": "^0.0.10"
|
||||
},
|
||||
"scripts": {
|
||||
"keycloak": "yarn build && build-keycloak-theme",
|
||||
},
|
||||
```
|
||||
|
||||
Then build your app with `yarn run build` or `npm run build`, you will be provided with instructions
|
||||
about how to load the theme into Keycloak.
|
||||
It is mandatory that you specify the url where your app will be available
|
||||
using the `homepage` field.
|
||||
|
||||
Once you've edited your `package.json` you can install your new
|
||||
dependency with `yarn install` and build the keycloak theme with
|
||||
`yarn keycloak`.
|
||||
|
||||
Once the build is complete instructions about how to load
|
||||
the theme into Keycloak are printed in the console.
|
||||
|
||||
### Specify from where the resources should be downloaded.
|
||||
|
||||
*TL;DR*: Building the theme with the `--external-assets` option enables the login
|
||||
page to load faster for first time users but it also implies that:
|
||||
- If the app is down, your Keycloak login and register pages are down as well.
|
||||
- Each time the app is updated, the theme must be updated as well.
|
||||
- CORS must be enabled for fonts.
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary>
|
||||
|
||||
When you run `npx build-keycloak-theme` without arguments, Keycloakify will build
|
||||
a standalone version of the Keycloak theme. That is to say even if your app, the
|
||||
one hosted at the url specified as `homepage`, is down the Keycloak theme will still work.
|
||||
It also mean that you won't have to update your theme on your Keycloak server each time
|
||||
your app is updated.
|
||||
In this mode, the default, every asset are served by the keycloak server.
|
||||
The drawback of this approach is that when users access the login page for the first time
|
||||
they have to download the whole app again.
|
||||
You probably have [long-term asset caching](https://create-react-app.dev/docs/production-build/#static-file-caching)
|
||||
enabled in the server that host your app ([example](https://github.com/garronej/keycloakify-demo-app/blob/224c43383548635a463fa68e8909c147ac189f0e/nginx.conf#L14))
|
||||
so it can be interesting to only serve the html from Keycloak server and everything
|
||||
else, your JS bundles, your CSS ect from the server that host your app.
|
||||
|
||||
To enable this behavior you car run:
|
||||
```bash
|
||||
npx build-keycloak-theme --external-assets
|
||||
```
|
||||
(instead of `npx build-keycloak-theme`)
|
||||
|
||||
This is something you probably want to do in your CI pipeline. [Example](https://github.com/garronej/keycloakify-demo-app/blob/224c43383548635a463fa68e8909c147ac189f0e/.github/workflows/ci.yaml#L112)
|
||||
|
||||
Also note that there is [a same-origin policy exception for fonts](https://en.wikipedia.org/wiki/Same-origin_policy#cite_note-3) so you must enabled
|
||||
CORS for fonts on the server hosting your app. Concretely this mean that your server should add a `Access-Control-Allow-Origin: *` response header to
|
||||
GET request on *.woff2?. [Example with Nginx](https://github.com/garronej/keycloakify-demo-app/blob/224c43383548635a463fa68e8909c147ac189f0e/nginx.conf#L18-L20)
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## Developing your login and register pages in your React app
|
||||
|
||||
TODO
|
||||
### Just changing the look
|
||||
|
||||
# How to implement context persistance
|
||||
The first approach is to only arr/replace the default class names by your
|
||||
own.
|
||||
|
||||
If you want dark mode preference, language and others users preferences your can do so
|
||||
very easily by using [`powerhooks/useGlobalState`](https://github.com/garronej/powerhooks)
|
||||
```tsx
|
||||
|
||||
WARNING: `powerhooks` is still a work in progress.
|
||||
import { App } from "./<wherever>/App";
|
||||
import {
|
||||
KcApp,
|
||||
defaultKcProps,
|
||||
kcContext
|
||||
} from "keycloakify";
|
||||
import { css } from "tss-react";
|
||||
|
||||
# REQUIREMENTS
|
||||
const myClassName = css({ "color": "red" });
|
||||
|
||||
reactDom.render(
|
||||
// Unless the app is currently being served by Keycloak
|
||||
// kcContext is undefined.
|
||||
kcContext !== undefined ?
|
||||
<KcApp
|
||||
kcContext={kcContext}
|
||||
{...{
|
||||
...defaultKcProps,
|
||||
"kcHeaderWrapperClass": myClassName
|
||||
}}
|
||||
/> :
|
||||
<App />, // Your actual app
|
||||
document.getElementById("root")
|
||||
);
|
||||
```
|
||||
|
||||
<i>result:</i>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/6702424/110261408-688d6280-7fb0-11eb-9822-7003d268b459.png">
|
||||
</p>
|
||||
|
||||
### Changing the look **and** feel
|
||||
|
||||
If you want to really re-implement the pages the best approach is to
|
||||
create you own version of the [`<KcApp />`](https://github.com/garronej/keycloakify/blob/develop/src/lib/components/KcApp.tsx).
|
||||
Copy/past some of [the components](https://github.com/garronej/keycloakify/tree/develop/src/lib/components) provided by this module and start hacking around.
|
||||
|
||||
### Hot reload
|
||||
|
||||
By default, in order to see your changes you will have to wait for
|
||||
`yarn build` to complete which can takes sevrall minute.
|
||||
|
||||
If you want to test your login screens outside of Keycloak, in [storybook](https://storybook.js.org/)
|
||||
for example you can use `kcContextMocks`.
|
||||
|
||||
```tsx
|
||||
import {
|
||||
KcApp,
|
||||
defaultKcProps,
|
||||
kcContextMocks
|
||||
} from "keycloakify";
|
||||
|
||||
reactDom.render(
|
||||
kcContext !== undefined ?
|
||||
<KcApp
|
||||
kcContext={kcContextMocks.kcLoginContext}
|
||||
{...defaultKcProps}
|
||||
/>
|
||||
document.getElementById("root")
|
||||
);
|
||||
```
|
||||
|
||||
then `yarn start` ...
|
||||
|
||||
Checkout [this concrete example](https://github.com/garronej/keycloakify-demo-app/blob/main/src/index.tsx)
|
||||
|
||||
|
||||
*NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded*
|
||||
[](https://youtu.be/xTz0Rj7i2v8)
|
||||
|
||||
# GitHub Actions
|
||||
|
||||

|
||||
|
||||
[Here is a demo repo](https://github.com/garronej/keycloakify-demo-app) to show how to automate
|
||||
the building and publishing of the theme (the .jar file).
|
||||
|
||||
# Requirements
|
||||
|
||||
Tested with the following Keycloak versions:
|
||||
- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore)
|
||||
- [12.0.4](https://hub.docker.com/layers/jboss/keycloak/12.0.4/images/sha256-67e0c88e69bd0c7aef972c40bdeb558a974013a28b3668ca790ed63a04d70584?context=explore)
|
||||
|
||||
This tool will be maintained to stay compatible with Keycloak v11 and up, however, the default pages you will get
|
||||
(before you customize it) will always be the ones of the Keycloak v11.
|
||||
|
||||
This tools assumes you are bundling your app with Webpack (tested with 4.44.2) .
|
||||
It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file
|
||||
and a `static/` directory generated by webpack.
|
||||
and a `build/static/` directory generated by webpack.
|
||||
|
||||
**All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3=)
|
||||
|
||||
- For building the theme: `mvn` (Maven) must be installed
|
||||
- For development, (testing the theme in a local container ): `rm`, `mkdir`, `wget`, `unzip` are assumed to be available.
|
||||
- For development (testing the theme in a local container ): `rm`, `mkdir`, `wget`, `unzip` are assumed to be available
|
||||
and `docker` up and running.
|
||||
|
||||
NOTE: This build tool has only be tested on MacOS.
|
||||
|
||||
# Limitations
|
||||
|
||||
## `process.env.PUBLIC_URL` not supported.
|
||||
|
||||
You won't be able to [import things from your public directory in your JavaScript code](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system). (This isn't recommended anyway).
|
||||
|
||||
## `@font-face` importing fonts from the `src/` dir
|
||||
|
||||
**If you are building the theme with `--external-assets` this limitation doesn't apply.**
|
||||
### Example of setup that **won't** work
|
||||
|
||||
- We have a `fonts/` directory in `src/`
|
||||
- We import the font like this [`src: url("/fonts/my-font.woff2") format("woff2");`(https://github.com/garronej/keycloakify-demo-app/blob/07d54a3012ef354ee12b1374c6f7ad1cb125d56b/src/fonts.scss#L4) in a `.scss` a file.
|
||||
|
||||
### Workarounds
|
||||
|
||||
If it is possible, use Google Fonts or any other font provider.
|
||||
|
||||
If you want to host your font recommended approach is to move your fonts into the `public`
|
||||
directory and to place your `@font-face` statements in the `public/index.html`.
|
||||
Example [here]().
|
||||
|
||||
You can also [use your explicit url](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/src/fonts.scss#L16) but don't forget [`Access-Control-Allow-Origin`](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/nginx.conf#L17-L19).
|
||||
|
||||
# Implement context persistence (optional)
|
||||
|
||||
If, before logging in, a user has selected a specific language
|
||||
you don't want it to be reset to default when the user gets redirected to
|
||||
the login or register pages.
|
||||
|
||||
Same goes for the dark mode, you don't want, if the user had it enabled
|
||||
to show the login page with light themes.
|
||||
|
||||
The problem is that you are probably using `localStorage` to persist theses values across
|
||||
reload but, as the Keycloak pages are not served on the same domain that the rest of your
|
||||
app you won't be able to carry over states using `localStorage`.
|
||||
|
||||
The only reliable solution is to inject parameters into the URL before
|
||||
redirecting to Keycloak. We integrate with
|
||||
[`keycloak-js`](https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/javascript-adapter.adoc),
|
||||
by providing you a way to tell `keycloak-js` that you would like to inject
|
||||
some search parameters before redirecting.
|
||||
|
||||
The method also works with [`@react-keycloak/web`](https://www.npmjs.com/package/@react-keycloak/web) (use the `initOptions`).
|
||||
|
||||
You can implement your own mechanism to pass the states in the URL and
|
||||
restore it on the other side but we recommend using `powerhooks/useGlobalState`
|
||||
from the library [`powerhooks`](https://www.powerhooks.dev) that provide an elegant
|
||||
way to handle states such as `isDarkModeEnabled` or `selectedLanguage`.
|
||||
|
||||
Let's modify [the example](https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/javascript-adapter.adoc) from the official `keycloak-js` documentation to
|
||||
enables the states of `useGlobalStates` to be injected in the URL before redirecting.
|
||||
Note that the states are automatically restored on the other side by `powerhooks`
|
||||
|
||||
```typescript
|
||||
import keycloak_js from "keycloak-js";
|
||||
import { injectGlobalStatesInSearchParams } from "powerhooks/useGlobalState";
|
||||
import { createKeycloakAdapter } from "keycloakify";
|
||||
|
||||
//...
|
||||
|
||||
const keycloakInstance = keycloak_js({
|
||||
"url": "http://keycloak-server/auth",
|
||||
"realm": "myrealm",
|
||||
"clientId": "myapp"
|
||||
});
|
||||
|
||||
keycloakInstance.init({
|
||||
"onLoad": 'check-sso',
|
||||
"silentCheckSsoRedirectUri": window.location.origin + "/silent-check-sso.html",
|
||||
"adapter": createKeycloakAdapter({
|
||||
"transformUrlBeforeRedirect": injectGlobalStatesInSearchParams,
|
||||
keycloakInstance
|
||||
})
|
||||
});
|
||||
|
||||
//...
|
||||
```
|
||||
|
||||
If you really want to go the extra miles and avoid having the white
|
||||
flash of the blank html before the js bundle have been evaluated
|
||||
[here is a snippet](https://github.com/InseeFrLab/onyxia-ui/blob/a77eb502870cfe6878edd0d956c646d28746d053/public/index.html#L5-L54) that you can place in your `public/index.html` if you are using `powerhooks/useGlobalState`.
|
||||
|
||||
# API Reference
|
||||
|
||||
## The build tool
|
||||
|
||||
Part of the lib that runs with node, at build time.
|
||||
|
||||
- `npx build-keycloak-theme`: Builds the theme, the CWD is assumed to be the root of your react project.
|
||||
- `npx download-sample-keycloak-themes`: Downloads the keycloak default themes (for development purposes)
|
||||
|
||||
## The fronted lib ( imported into your react
|
||||
|
||||
Part of the lib that you import in your react project and runs on the browser.
|
||||
|
||||
**TODO**
|
||||
- `npx build-keycloak-theme [--external-assets]`: Builds the theme, the CWD is assumed to be the root of your react project.
|
||||
- `npx download-sample-keycloak-themes`: Downloads the keycloak default themes (for development purposes)
|
1708
package-lock.json
generated
1708
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@ -1,23 +1,25 @@
|
||||
{
|
||||
"name": "keycloak-react-theming",
|
||||
"version": "0.0.12",
|
||||
"name": "keycloakify",
|
||||
"version": "0.3.10",
|
||||
"description": "Keycloak theme generator for Reacts app",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/garronej/keycloak-react-theming.git"
|
||||
"url": "git://github.com/garronej/keycloakify.git"
|
||||
},
|
||||
"main": "dist/lib/index.js",
|
||||
"types": "dist/lib/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc && npm run grant-exec-perms && npm run copy-standalone-ha",
|
||||
"copy-standalone-ha": "cp src/bin/build-keycloak-theme/generateDebugFiles/standalone-ha.xml dist/bin/build-keycloak-theme/generateDebugFiles/",
|
||||
"grant-exec-perms": "cd dist/bin && chmod +x build-keycloak-theme/index.js download-sample-keycloak-themes.js",
|
||||
"test": "node dist/test/build-keycloak-theme && node dist/test/download-sample-keycloak-themes",
|
||||
"enable_short_import_path": "npm run build && denoify_enable_short_npm_import_path"
|
||||
"clean": "rimraf dist/",
|
||||
"build": "yarn clean && tsc && yarn grant-exec-perms && yarn copy-files",
|
||||
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
||||
"test": "node dist/test",
|
||||
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/",
|
||||
"generate-messages": "node dist/bin/generate-i18n-messages.js",
|
||||
"watch": "tsc -w"
|
||||
},
|
||||
"bin": {
|
||||
"build-keycloak-theme": "dist/bin/build-keycloak-theme/index.js",
|
||||
"download-sample-keycloak-themes": "dist/bin/download-sample-keycloak-themes.js"
|
||||
"install-builtin-keycloak-themes": "dist/bin/install-builtin-keycloak-themes.js"
|
||||
},
|
||||
"author": "u/garronej",
|
||||
"license": "MIT",
|
||||
@ -31,17 +33,29 @@
|
||||
"keywords": [
|
||||
"keycloak",
|
||||
"react",
|
||||
"theme"
|
||||
"theme",
|
||||
"FreeMarker",
|
||||
"ftl",
|
||||
"login",
|
||||
"register"
|
||||
],
|
||||
"homepage": "https://github.com/garronej/keycloak-react-theming",
|
||||
"homepage": "https://github.com/garronej/keycloakify",
|
||||
"devDependencies": {
|
||||
"@types/node": "^10.0.0",
|
||||
"@types/react": "^17.0.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"denoify": "^0.6.4",
|
||||
"scripting-tools": "^0.19.13",
|
||||
"typescript": "^4.1.5"
|
||||
"properties-parser": "^0.3.1",
|
||||
"react": "^17.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "^1.0.0-rc.5"
|
||||
"cheerio": "^1.0.0-rc.5",
|
||||
"evt": "2.0.0-beta.15",
|
||||
"minimal-polyfills": "^2.1.6",
|
||||
"path": "^0.12.7",
|
||||
"powerhooks": "^0.0.27",
|
||||
"scripting-tools": "^0.19.13",
|
||||
"tss-react": "^0.0.11"
|
||||
}
|
||||
}
|
||||
|
2
src/bin/build-keycloak-theme/ftlValuesGlobalName.ts
Normal file
2
src/bin/build-keycloak-theme/ftlValuesGlobalName.ts
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
export const ftlValuesGlobalName = "kcContext";
|
@ -41,6 +41,8 @@ export function generateDebugFiles(
|
||||
[
|
||||
"#!/bin/bash",
|
||||
"",
|
||||
`cd ${keycloakThemeBuildingDirPath}`,
|
||||
"",
|
||||
`docker rm ${containerName} || true`,
|
||||
"",
|
||||
`docker build . -t ${dockerImage}`,
|
||||
|
@ -1,108 +0,0 @@
|
||||
|
||||
|
||||
import cheerio from "cheerio";
|
||||
import {
|
||||
replaceImportFromStaticInJsCode,
|
||||
generateCssCodeToDefineGlobals
|
||||
} from "./replaceImportFromStatic";
|
||||
|
||||
export function generateFtlFilesCodeFactory(
|
||||
params: {
|
||||
ftlValuesGlobalName: string;
|
||||
cssGlobalsToDefine: Record<string, string>;
|
||||
indexHtmlCode: string;
|
||||
}
|
||||
) {
|
||||
|
||||
const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode } = params;
|
||||
|
||||
const $ = cheerio.load(indexHtmlCode);
|
||||
|
||||
$("script:not([src])").each((...[, element]) => {
|
||||
|
||||
const { fixedJsCode } = replaceImportFromStaticInJsCode({
|
||||
ftlValuesGlobalName,
|
||||
"jsCode": $(element).html()!
|
||||
});
|
||||
|
||||
$(element).text(fixedJsCode);
|
||||
|
||||
});
|
||||
|
||||
|
||||
([
|
||||
["link", "href"],
|
||||
["script", "src"],
|
||||
] as const).forEach(([selector, attrName]) =>
|
||||
$(selector).each((...[, element]) => {
|
||||
|
||||
const href = $(element).attr(attrName);
|
||||
|
||||
if (!href?.startsWith("/")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(element).attr(attrName, "${url.resourcesPath}" + href);
|
||||
|
||||
})
|
||||
);
|
||||
|
||||
$("head").prepend(
|
||||
[
|
||||
...(Object.keys(cssGlobalsToDefine).length === 0 ? [] : [
|
||||
'',
|
||||
'<style>',
|
||||
generateCssCodeToDefineGlobals(
|
||||
{ cssGlobalsToDefine }
|
||||
).cssCodeToPrependInHead,
|
||||
'</style>',
|
||||
''
|
||||
]),
|
||||
|
||||
'<script>',
|
||||
' Object.assign(',
|
||||
` window.${ftlValuesGlobalName},`,
|
||||
' {',
|
||||
' "url": {',
|
||||
' "loginAction": "${url.loginAction}",',
|
||||
' "resourcesPath": "${url.resourcesPath}"',
|
||||
' }',
|
||||
' }',
|
||||
' );',
|
||||
'</script>',
|
||||
''
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
|
||||
const partiallyFixedIndexHtmlCode = $.html();
|
||||
|
||||
function generateFtlFilesCode(
|
||||
params: {
|
||||
pageBasename: "login.ftl" | "register.ftl"
|
||||
}
|
||||
): { ftlCode: string; } {
|
||||
|
||||
const { pageBasename } = params;
|
||||
|
||||
const $ = cheerio.load(partiallyFixedIndexHtmlCode);
|
||||
|
||||
$("head").prepend(
|
||||
[
|
||||
'',
|
||||
'<script>',
|
||||
` window.${ftlValuesGlobalName} = { "pageBasename": "${pageBasename}" };`,
|
||||
'</script>',
|
||||
''
|
||||
].join("\n"),
|
||||
|
||||
);
|
||||
|
||||
return { "ftlCode": $.html() };
|
||||
|
||||
}
|
||||
|
||||
return { generateFtlFilesCode };
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
|
||||
Object.defineProperty(
|
||||
Object,
|
||||
"deepAssign",
|
||||
{
|
||||
"value": function callee(target, source) {
|
||||
Object.keys(source).forEach(function (key) {
|
||||
var value = source[key];
|
||||
if (target[key] === undefined) {
|
||||
target[key] = value;
|
||||
return;
|
||||
}
|
||||
if (value instanceof Object) {
|
||||
if (value instanceof Array) {
|
||||
value.forEach(function (entry) {
|
||||
target[key].push(entry);
|
||||
});
|
||||
return;
|
||||
}
|
||||
callee(target[key], value);
|
||||
return;
|
||||
}
|
||||
target[key] = value;
|
||||
});
|
||||
return target;
|
||||
}
|
||||
}
|
||||
);
|
@ -0,0 +1,26 @@
|
||||
|
||||
var es = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g;
|
||||
|
||||
var unes = {
|
||||
'&': '&',
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'>': '>',
|
||||
''': "'",
|
||||
''': "'",
|
||||
'"': '"',
|
||||
'"': '"'
|
||||
};
|
||||
var cape = function (m) { return unes[m]; };
|
||||
|
||||
Object.defineProperty(
|
||||
String,
|
||||
"htmlUnescape",
|
||||
{
|
||||
"value": function (un) {
|
||||
return String.prototype.replace.call(un, es, cape);
|
||||
}
|
||||
}
|
||||
);
|
15
src/bin/build-keycloak-theme/generateFtl/error.ftl
Normal file
15
src/bin/build-keycloak-theme/generateFtl/error.ftl
Normal file
@ -0,0 +1,15 @@
|
||||
<script>const _=
|
||||
{
|
||||
"client": (function (){
|
||||
|
||||
<#if client??>
|
||||
return {
|
||||
"baseUrl": "${(client.baseUrl!'')?no_esc}" || undefined
|
||||
};
|
||||
</#if>
|
||||
|
||||
return undefined;
|
||||
|
||||
})()
|
||||
}
|
||||
</script>
|
203
src/bin/build-keycloak-theme/generateFtl/index.ts
Normal file
203
src/bin/build-keycloak-theme/generateFtl/index.ts
Normal file
@ -0,0 +1,203 @@
|
||||
|
||||
|
||||
import cheerio from "cheerio";
|
||||
import {
|
||||
replaceImportsFromStaticInJsCode,
|
||||
replaceImportsInInlineCssCode,
|
||||
generateCssCodeToDefineGlobals
|
||||
} from "../replaceImportFromStatic";
|
||||
import fs from "fs";
|
||||
import { join as pathJoin } from "path";
|
||||
import { objectKeys } from "evt/tools/typeSafety/objectKeys";
|
||||
|
||||
export const pageIds = ["login.ftl", "register.ftl", "info.ftl", "error.ftl", "login-reset-password.ftl", "login-verify-email.ftl"] as const;
|
||||
|
||||
export type PageId = typeof pageIds[number];
|
||||
|
||||
function loadAdjacentFile(fileBasename: string) {
|
||||
return fs.readFileSync(pathJoin(__dirname, fileBasename))
|
||||
.toString("utf8");
|
||||
};
|
||||
|
||||
function loadFtlFile(ftlFileBasename: PageId | "template.ftl") {
|
||||
try {
|
||||
|
||||
return loadAdjacentFile(ftlFileBasename)
|
||||
.match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1];
|
||||
|
||||
} catch {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
export type Mode = {
|
||||
type: "standalone";
|
||||
urlPathname: string;
|
||||
} | {
|
||||
type: "external assets";
|
||||
urlPathname: string;
|
||||
urlOrigin: string;
|
||||
}
|
||||
|
||||
export function generateFtlFilesCodeFactory(
|
||||
params: {
|
||||
ftlValuesGlobalName: string;
|
||||
cssGlobalsToDefine: Record<string, string>;
|
||||
indexHtmlCode: string;
|
||||
mode: Mode;
|
||||
}
|
||||
) {
|
||||
|
||||
const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode, mode } = params;
|
||||
|
||||
const $ = cheerio.load(indexHtmlCode);
|
||||
|
||||
$("script:not([src])").each((...[, element]) => {
|
||||
|
||||
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
||||
ftlValuesGlobalName,
|
||||
"jsCode": $(element).html()!,
|
||||
mode
|
||||
});
|
||||
|
||||
$(element).text(fixedJsCode);
|
||||
|
||||
});
|
||||
|
||||
$("style").each((...[, element]) => {
|
||||
|
||||
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
||||
"cssCode": $(element).html()!,
|
||||
mode
|
||||
});
|
||||
|
||||
$(element).text(fixedCssCode);
|
||||
|
||||
});
|
||||
|
||||
([
|
||||
["link", "href"],
|
||||
["script", "src"],
|
||||
] as const).forEach(([selector, attrName]) =>
|
||||
$(selector).each((...[, element]) => {
|
||||
|
||||
const href = $(element).attr(attrName);
|
||||
|
||||
if (href === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mode.type) {
|
||||
case "external assets":
|
||||
$(element).attr(
|
||||
attrName,
|
||||
href.replace(/^\//, `${mode.urlOrigin}/`)
|
||||
);
|
||||
break;
|
||||
case "standalone":
|
||||
$(element).attr(
|
||||
attrName,
|
||||
href.replace(
|
||||
new RegExp(`^${mode.urlPathname.replace(/\//g, "\\/")}`),
|
||||
"${url.resourcesPath}/build/"
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
);
|
||||
|
||||
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
|
||||
const ftlCommonPlaceholders = {
|
||||
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadFtlFile("template.ftl"),
|
||||
'<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->':
|
||||
[
|
||||
'<#if scripts??>',
|
||||
' <#list scripts as script>',
|
||||
' <script src="${script}" type="text/javascript"></script>',
|
||||
' </#list>',
|
||||
'</#if>'
|
||||
].join("\n")
|
||||
};
|
||||
|
||||
const pageSpecificCodePlaceholder = "<!-- dIddLqMeOedErIdLsPdNdI9dSl42sw -->";
|
||||
|
||||
$("head").prepend(
|
||||
[
|
||||
...(Object.keys(cssGlobalsToDefine).length === 0 ? [] : [
|
||||
'',
|
||||
'<style>',
|
||||
generateCssCodeToDefineGlobals({
|
||||
cssGlobalsToDefine,
|
||||
"urlPathname": mode.urlPathname
|
||||
}).cssCodeToPrependInHead,
|
||||
'</style>',
|
||||
''
|
||||
]),
|
||||
...["Object.deepAssign.js", "String.htmlUnescape.js"].map(
|
||||
fileBasename => [
|
||||
"<script>",
|
||||
loadAdjacentFile(fileBasename),
|
||||
"</script>"
|
||||
].join("\n")
|
||||
),
|
||||
'<script>',
|
||||
` window.${ftlValuesGlobalName}= Object.assign(`,
|
||||
` {},`,
|
||||
` ${objectKeys(ftlCommonPlaceholders)[0]}`,
|
||||
' );',
|
||||
'</script>',
|
||||
'',
|
||||
pageSpecificCodePlaceholder,
|
||||
'',
|
||||
objectKeys(ftlCommonPlaceholders)[1]
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const partiallyFixedIndexHtmlCode = $.html();
|
||||
|
||||
function generateFtlFilesCode(
|
||||
params: {
|
||||
pageId: PageId;
|
||||
}
|
||||
): { ftlCode: string; } {
|
||||
|
||||
const { pageId } = params;
|
||||
|
||||
const $ = cheerio.load(partiallyFixedIndexHtmlCode);
|
||||
|
||||
const ftlPlaceholders = {
|
||||
'{ "x": "kxOlLqMeOed9sdLdIdOxd444" }': loadFtlFile(pageId),
|
||||
...ftlCommonPlaceholders
|
||||
};
|
||||
|
||||
let ftlCode = $.html()
|
||||
.replace(
|
||||
pageSpecificCodePlaceholder,
|
||||
[
|
||||
'<script>',
|
||||
` Object.deepAssign(`,
|
||||
` window.${ftlValuesGlobalName},`,
|
||||
` { "pageId": "${pageId}" }`,
|
||||
' );',
|
||||
` Object.deepAssign(`,
|
||||
` window.${ftlValuesGlobalName},`,
|
||||
` ${objectKeys(ftlPlaceholders)[0]}`,
|
||||
' );',
|
||||
'</script>'
|
||||
].join("\n")
|
||||
);
|
||||
|
||||
objectKeys(ftlPlaceholders)
|
||||
.forEach(id => ftlCode = ftlCode.replace(id, ftlPlaceholders[id]));
|
||||
|
||||
return { ftlCode };
|
||||
|
||||
}
|
||||
|
||||
return { generateFtlFilesCode };
|
||||
|
||||
|
||||
}
|
37
src/bin/build-keycloak-theme/generateFtl/info.ftl
Normal file
37
src/bin/build-keycloak-theme/generateFtl/info.ftl
Normal file
@ -0,0 +1,37 @@
|
||||
<script>const _=
|
||||
{
|
||||
"messageHeader": "${messageHeader!''}" || undefined,
|
||||
"requiredActions": (function (){
|
||||
|
||||
<#if requiredActions??>
|
||||
|
||||
var out =[];
|
||||
|
||||
<#list requiredActions>
|
||||
<#items as reqActionItem>
|
||||
out.push("${reqActionItem}");
|
||||
</#items></b>
|
||||
</#list>
|
||||
|
||||
return out;
|
||||
|
||||
<#else>
|
||||
|
||||
return undefined;
|
||||
|
||||
})(),
|
||||
"skipLink": (function (){
|
||||
|
||||
<#if skipLink??>
|
||||
return true;
|
||||
</#if>
|
||||
return false;
|
||||
|
||||
})(),
|
||||
"pageRedirectUri": "${(pageRedirectUri!'')?no_esc}" || undefined,
|
||||
"actionUri": "${(actionUri!'')?no_esc}" || undefined,
|
||||
"client": {
|
||||
"baseUrl": "${(client.baseUrl!'')?no_esc}" || undefined
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,7 @@
|
||||
<script>const _=
|
||||
{
|
||||
"realm": {
|
||||
"loginWithEmailAllowed": ${realm.loginWithEmailAllowed?c}
|
||||
},
|
||||
}
|
||||
</script>
|
83
src/bin/build-keycloak-theme/generateFtl/login.ftl
Normal file
83
src/bin/build-keycloak-theme/generateFtl/login.ftl
Normal file
@ -0,0 +1,83 @@
|
||||
<script>const _=
|
||||
{
|
||||
"url": {
|
||||
"loginResetCredentialsUrl": "${url.loginResetCredentialsUrl?no_esc}",
|
||||
"registrationUrl": "${url.registrationUrl?no_esc}"
|
||||
},
|
||||
"realm": {
|
||||
"loginWithEmailAllowed": ${realm.loginWithEmailAllowed?c},
|
||||
"rememberMe": ${realm.rememberMe?c},
|
||||
"password": ${realm.password?c},
|
||||
"resetPasswordAllowed": ${realm.resetPasswordAllowed?c},
|
||||
"registrationAllowed": ${realm.registrationAllowed?c}
|
||||
},
|
||||
"auth": (function (){
|
||||
|
||||
<#if auth?has_content>
|
||||
|
||||
var out= {
|
||||
"selectedCredential": "${auth.selectedCredential!''}" || undefined
|
||||
};
|
||||
|
||||
return out;
|
||||
|
||||
</#if>
|
||||
|
||||
return undefined;
|
||||
|
||||
})(),
|
||||
"social": {
|
||||
"displayInfo": ${social.displayInfo?c},
|
||||
"providers": (()=>{
|
||||
|
||||
<#if social.providers??>
|
||||
|
||||
var out= [];
|
||||
|
||||
<#list social.providers as p>
|
||||
out.push({
|
||||
"loginUrl": "${p.loginUrl?no_esc}",
|
||||
"alias": "${p.alias}",
|
||||
"providerId": "${p.providerId}",
|
||||
"displayName": "${p.displayName}"
|
||||
});
|
||||
</#list>
|
||||
|
||||
return out;
|
||||
|
||||
</#if>
|
||||
|
||||
return undefined;
|
||||
|
||||
})()
|
||||
},
|
||||
"usernameEditDisabled": (function () {
|
||||
|
||||
<#if usernameEditDisabled??>
|
||||
return true;
|
||||
</#if>
|
||||
return false;
|
||||
|
||||
})(),
|
||||
"login": {
|
||||
"username": "${login.username!''}" || undefined,
|
||||
"rememberMe": (function (){
|
||||
|
||||
<#if login.rememberMe??>
|
||||
return true;
|
||||
</#if>
|
||||
return false;
|
||||
|
||||
|
||||
})()
|
||||
},
|
||||
"registrationDisabled": (function (){
|
||||
|
||||
<#if registrationDisabled??>
|
||||
return true;
|
||||
</#if>
|
||||
return false;
|
||||
|
||||
})()
|
||||
}
|
||||
</script>
|
46
src/bin/build-keycloak-theme/generateFtl/register.ftl
Normal file
46
src/bin/build-keycloak-theme/generateFtl/register.ftl
Normal file
@ -0,0 +1,46 @@
|
||||
<script>const _=
|
||||
{
|
||||
"url": {
|
||||
"registrationAction": "${url.registrationAction?no_esc}"
|
||||
},
|
||||
"messagesPerField": {
|
||||
"printIfExists": function (key, x) {
|
||||
switch(key){
|
||||
case "userLabel": "${messagesPerField.printIfExists('userLabel','1')}" ? x : undefined;
|
||||
case "username": "${messagesPerField.printIfExists('username','1')}" ? x : undefined;
|
||||
case "email": "${messagesPerField.printIfExists('email','1')}" ? x : undefined;
|
||||
case "firstName": "${messagesPerField.printIfExists('firstName','1')}" ? x : undefined;
|
||||
case "lastName": "${messagesPerField.printIfExists('lastName','1')}" ? x : undefined;
|
||||
case "password": "${messagesPerField.printIfExists('password','1')}" ? x : undefined;
|
||||
case "password-confirm": "${messagesPerField.printIfExists('password-confirm','1')}" ? x : undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
"register": {
|
||||
"formData": {
|
||||
"firstName": "${register.formData.firstName!''}" || undefined,
|
||||
"displayName": "${register.formData.displayName!''}" || undefined,
|
||||
"lastName": "${register.formData.lastName!''}" || undefined,
|
||||
"email": "${register.formData.email!''}" || undefined,
|
||||
"username": "${register.formData.username!''}" || undefined
|
||||
}
|
||||
},
|
||||
"passwordRequired": (function (){
|
||||
|
||||
<#if passwordRequired??>
|
||||
return true;
|
||||
</#if>
|
||||
return false;
|
||||
|
||||
})(),
|
||||
"recaptchaRequired": (function (){
|
||||
|
||||
<#if passwordRequired??>
|
||||
return true;
|
||||
</#if>
|
||||
return false;
|
||||
|
||||
})(),
|
||||
"recaptchaSiteKey": "${recaptchaSiteKey!''}" || undefined
|
||||
}
|
||||
</script>
|
114
src/bin/build-keycloak-theme/generateFtl/template.ftl
Normal file
114
src/bin/build-keycloak-theme/generateFtl/template.ftl
Normal file
@ -0,0 +1,114 @@
|
||||
<script>const _=
|
||||
{
|
||||
"url": {
|
||||
"loginAction": "${url.loginAction?no_esc}",
|
||||
"resourcesPath": "${url.resourcesPath?no_esc}",
|
||||
"resourcesCommonPath": "${url.resourcesCommonPath?no_esc}",
|
||||
"loginRestartFlowUrl": "${url.loginRestartFlowUrl?no_esc}",
|
||||
"loginUrl": "${url.loginUrl?no_esc}"
|
||||
},
|
||||
"realm": {
|
||||
"displayName": "${realm.displayName!''}" || undefined,
|
||||
"displayNameHtml": "${realm.displayNameHtml!''}" || undefined,
|
||||
"internationalizationEnabled": ${realm.internationalizationEnabled?c},
|
||||
"registrationEmailAsUsername": ${realm.registrationEmailAsUsername?c},
|
||||
},
|
||||
"locale": (function (){
|
||||
|
||||
<#if realm.internationalizationEnabled>
|
||||
|
||||
return {
|
||||
"supported": (function(){
|
||||
|
||||
<#if realm.internationalizationEnabled>
|
||||
|
||||
var out= [];
|
||||
|
||||
<#list locale.supported as lng>
|
||||
out.push({
|
||||
"url": "${lng.url?no_esc}",
|
||||
"label": "${lng.label}",
|
||||
"languageTag": "${lng.languageTag}"
|
||||
});
|
||||
</#list>
|
||||
|
||||
return out;
|
||||
|
||||
</#if>
|
||||
|
||||
return undefined;
|
||||
|
||||
})(),
|
||||
"current": "${locale.current}"
|
||||
};
|
||||
|
||||
</#if>
|
||||
|
||||
return undefined;
|
||||
|
||||
})(),
|
||||
"auth": (function (){
|
||||
|
||||
|
||||
<#if auth?has_content>
|
||||
|
||||
var out= {
|
||||
"showUsername": ${auth.showUsername()?c},
|
||||
"showResetCredentials": ${auth.showResetCredentials()?c},
|
||||
"showTryAnotherWayLink": ${auth.showTryAnotherWayLink()?c},
|
||||
};
|
||||
|
||||
<#if auth.showUsername() && !auth.showResetCredentials()>
|
||||
Object.assign(
|
||||
out,
|
||||
{
|
||||
"attemptedUsername": "${auth.attemptedUsername}"
|
||||
}
|
||||
);
|
||||
</#if>
|
||||
|
||||
return out;
|
||||
|
||||
</#if>
|
||||
|
||||
|
||||
return undefined;
|
||||
|
||||
})(),
|
||||
"scripts": (function(){
|
||||
|
||||
var out = [];
|
||||
|
||||
<#if scripts??>
|
||||
<#list scripts as script>
|
||||
out.push("${script}");
|
||||
</#list>
|
||||
</#if>
|
||||
|
||||
return out;
|
||||
|
||||
})(),
|
||||
"message": (function (){
|
||||
|
||||
<#if message?has_content>
|
||||
|
||||
return {
|
||||
"type": "${message.type}",
|
||||
"summary": String.htmlUnescape("${message.summary}")
|
||||
};
|
||||
|
||||
</#if>
|
||||
|
||||
return undefined;
|
||||
|
||||
})(),
|
||||
"isAppInitiatedAction": (function (){
|
||||
|
||||
<#if isAppInitiatedAction??>
|
||||
return true;
|
||||
</#if>
|
||||
return false;
|
||||
|
||||
})()
|
||||
}
|
||||
</script>
|
@ -1,57 +1,78 @@
|
||||
|
||||
import { transformCodebase } from "../../tools/transformCodebase";
|
||||
import { transformCodebase } from "../tools/transformCodebase";
|
||||
import * as fs from "fs";
|
||||
import { join as pathJoin } from "path";
|
||||
import {
|
||||
replaceImportFromStaticInCssCode,
|
||||
replaceImportFromStaticInJsCode
|
||||
replaceImportsInCssCode,
|
||||
replaceImportsFromStaticInJsCode
|
||||
} from "./replaceImportFromStatic";
|
||||
import { generateFtlFilesCodeFactory } from "./generateFtl";
|
||||
import { generateFtlFilesCodeFactory, pageIds, Mode } from "./generateFtl";
|
||||
import { builtinThemesUrl } from "../install-builtin-keycloak-themes";
|
||||
import { downloadAndUnzip } from "../tools/downloadAndUnzip";
|
||||
import * as child_process from "child_process";
|
||||
import { ftlValuesGlobalName } from "./ftlValuesGlobalName";
|
||||
import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/kcContextMocks/urlResourcesPath";
|
||||
import { isInside } from "../tools/isInside";
|
||||
|
||||
const ftlValuesGlobalName = "keycloakFtlValues";
|
||||
|
||||
export function generateKeycloakThemeResources(
|
||||
params: {
|
||||
themeName: string;
|
||||
reactAppBuildDirPath: string;
|
||||
keycloakThemeBuildingDirPath: string;
|
||||
mode: Mode;
|
||||
}
|
||||
) {
|
||||
|
||||
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath } = params;
|
||||
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath, mode } = params;
|
||||
|
||||
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
|
||||
|
||||
let allCssGlobalsToDefine: Record<string, string> = {};
|
||||
|
||||
transformCodebase({
|
||||
"destDirPath": pathJoin(themeDirPath, "resources"),
|
||||
"destDirPath": pathJoin(themeDirPath, "resources", "build"),
|
||||
"srcDirPath": reactAppBuildDirPath,
|
||||
"transformSourceCodeString": ({ filePath, sourceCode }) => {
|
||||
|
||||
if (/\.css?$/i.test(filePath)) {
|
||||
|
||||
const { cssGlobalsToDefine, fixedCssCode } = replaceImportFromStaticInCssCode(
|
||||
{ "cssCode": sourceCode.toString("utf8") }
|
||||
);
|
||||
|
||||
allCssGlobalsToDefine = {
|
||||
...allCssGlobalsToDefine,
|
||||
...cssGlobalsToDefine
|
||||
};
|
||||
|
||||
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
|
||||
"transformSourceCode": ({ filePath, sourceCode }) => {
|
||||
|
||||
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
|
||||
if (
|
||||
isInside({
|
||||
"dirPath": pathJoin(reactAppBuildDirPath, subDirOfPublicDirBasename),
|
||||
filePath
|
||||
})
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (/\.js?$/i.test(filePath)) {
|
||||
if (mode.type === "standalone") {
|
||||
|
||||
const { fixedJsCode } = replaceImportFromStaticInJsCode({
|
||||
"jsCode": sourceCode.toString("utf8"),
|
||||
ftlValuesGlobalName
|
||||
});
|
||||
if (/\.css?$/i.test(filePath)) {
|
||||
|
||||
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
|
||||
const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode(
|
||||
{ "cssCode": sourceCode.toString("utf8") }
|
||||
);
|
||||
|
||||
allCssGlobalsToDefine = {
|
||||
...allCssGlobalsToDefine,
|
||||
...cssGlobalsToDefine
|
||||
};
|
||||
|
||||
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
|
||||
|
||||
}
|
||||
|
||||
if (/\.js?$/i.test(filePath)) {
|
||||
|
||||
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
||||
"jsCode": sourceCode.toString("utf8"),
|
||||
ftlValuesGlobalName,
|
||||
mode
|
||||
});
|
||||
|
||||
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -65,23 +86,79 @@ export function generateKeycloakThemeResources(
|
||||
ftlValuesGlobalName,
|
||||
"indexHtmlCode": fs.readFileSync(
|
||||
pathJoin(reactAppBuildDirPath, "index.html")
|
||||
).toString("utf8")
|
||||
).toString("utf8"),
|
||||
mode
|
||||
});
|
||||
|
||||
(["login.ftl", "register.ftl"] as const).forEach(pageBasename => {
|
||||
pageIds.forEach(pageId => {
|
||||
|
||||
const { ftlCode } = generateFtlFilesCode({ pageBasename });
|
||||
const { ftlCode } = generateFtlFilesCode({ pageId });
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(themeDirPath, pageBasename),
|
||||
pathJoin(themeDirPath, pageId),
|
||||
Buffer.from(ftlCode, "utf8")
|
||||
)
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
{
|
||||
|
||||
const tmpDirPath = pathJoin(themeDirPath, "..", "tmp_xxKdLpdIdLd");
|
||||
|
||||
downloadAndUnzip({
|
||||
"url": builtinThemesUrl,
|
||||
"destDirPath": tmpDirPath
|
||||
});
|
||||
|
||||
const themeResourcesDirPath = pathJoin(themeDirPath, "resources");
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "login", "resources"),
|
||||
"destDirPath": themeResourcesDirPath
|
||||
});
|
||||
|
||||
const reactAppPublicDirPath = pathJoin(reactAppBuildDirPath, "..", "public");
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": themeResourcesDirPath,
|
||||
"destDirPath": pathJoin(
|
||||
reactAppPublicDirPath,
|
||||
resourcesPath
|
||||
)
|
||||
});
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"),
|
||||
"destDirPath": pathJoin(
|
||||
reactAppPublicDirPath,
|
||||
resourcesCommonPath
|
||||
)
|
||||
});
|
||||
|
||||
const keycloakResourcesWithinPublicDirPath =
|
||||
pathJoin(reactAppPublicDirPath, subDirOfPublicDirBasename);
|
||||
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(keycloakResourcesWithinPublicDirPath, "README.txt"),
|
||||
Buffer.from([
|
||||
"This is just a test folder that helps develop",
|
||||
"the login and register page without having to yarn build"
|
||||
].join(" "))
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(keycloakResourcesWithinPublicDirPath, ".gitignore"),
|
||||
Buffer.from("*", "utf8")
|
||||
);
|
||||
|
||||
child_process.execSync(`rm -r ${tmpDirPath}`);
|
||||
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(themeDirPath, "theme.properties"),
|
||||
Buffer.from("parent=base\n", "utf8")
|
||||
Buffer.from("parent=keycloak", "utf8")
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -6,23 +6,70 @@ import type { ParsedPackageJson } from "./generateJavaStackFiles";
|
||||
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path";
|
||||
import * as child_process from "child_process";
|
||||
import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles";
|
||||
import { URL } from "url";
|
||||
|
||||
|
||||
const reactProjectDirPath = process.cwd();
|
||||
|
||||
const doUseExternalAssets = process.argv[2]?.toLowerCase() === "--external-assets";
|
||||
|
||||
const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json"));
|
||||
|
||||
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
|
||||
|
||||
|
||||
console.log("🔏 Building the keycloak theme...⌚");
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
console.log("🔏 Building the keycloak theme...⌚");
|
||||
|
||||
generateKeycloakThemeResources({
|
||||
keycloakThemeBuildingDirPath,
|
||||
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
|
||||
"themeName": parsedPackageJson.name
|
||||
"themeName": parsedPackageJson.name,
|
||||
"mode": (() => {
|
||||
|
||||
|
||||
const url = (() => {
|
||||
|
||||
const { homepage } = parsedPackageJson;
|
||||
|
||||
return homepage === undefined ?
|
||||
undefined :
|
||||
new URL(homepage);
|
||||
|
||||
})();
|
||||
|
||||
const urlPathname =
|
||||
url === undefined ?
|
||||
"/" :
|
||||
url.pathname.replace(/([^/])$/, "$1/");
|
||||
|
||||
|
||||
|
||||
return !doUseExternalAssets ?
|
||||
{
|
||||
"type": "standalone",
|
||||
urlPathname
|
||||
} as const
|
||||
:
|
||||
{
|
||||
"type": "external assets",
|
||||
urlPathname,
|
||||
"urlOrigin": (() => {
|
||||
|
||||
if (url === undefined) {
|
||||
console.error("ERROR: You must specify 'homepage' in your package.json");
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
return url.origin;
|
||||
|
||||
})()
|
||||
|
||||
} as const;
|
||||
|
||||
})()
|
||||
|
||||
});
|
||||
|
||||
const { jarFilePath } = generateJavaStackFiles({
|
||||
@ -65,11 +112,17 @@ if (require.main === module) {
|
||||
' - name: extensions',
|
||||
' mountPath: /opt/jboss/keycloak/standalone/deployments',
|
||||
'',
|
||||
`To enable the theme within keycloak log into the admin console, go to your realm settings, click on the theme tab then select ${parsedPackageJson.name} `,
|
||||
'',
|
||||
'To test your theme locally you can spin up a Keycloak container image with the theme loaded by running:',
|
||||
'To test your theme locally, with hot reloading, you can spin up a Keycloak container image with the theme loaded by running:',
|
||||
'',
|
||||
`👉 $ ./${pathRelative(reactProjectDirPath, pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename))} 👈`,
|
||||
'',
|
||||
'To enable the theme within keycloak log into the admin console ( 👉 http://localhost:8080 username: admin, password: admin 👈), create a realm (called "myrealm" for example),',
|
||||
`go to your realm settings, click on the theme tab then select ${parsedPackageJson.name}.`,
|
||||
`More details: https://www.keycloak.org/getting-started/getting-started-docker`,
|
||||
'',
|
||||
'Once your container is up and configured 👉 http://localhost:8080/auth/realms/myrealm/account 👈',
|
||||
'',
|
||||
`$ ./${pathRelative(reactProjectDirPath, pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename))}`
|
||||
].join("\n"));
|
||||
|
||||
}
|
||||
|
@ -1,25 +1,66 @@
|
||||
|
||||
import * as crypto from "crypto";
|
||||
|
||||
export function replaceImportFromStaticInJsCode(
|
||||
type Mode = {
|
||||
type: "standalone";
|
||||
} | {
|
||||
type: "external assets";
|
||||
urlOrigin: string;
|
||||
urlPathname: string;
|
||||
}
|
||||
|
||||
export function replaceImportsFromStaticInJsCode(
|
||||
params: {
|
||||
ftlValuesGlobalName: string;
|
||||
jsCode: string;
|
||||
mode: Mode;
|
||||
}
|
||||
): { fixedJsCode: string; } {
|
||||
|
||||
const { jsCode, ftlValuesGlobalName } = params;
|
||||
const { jsCode, ftlValuesGlobalName, mode } = params;
|
||||
|
||||
const fixedJsCode = jsCode!.replace(
|
||||
/"static\//g,
|
||||
`window.${ftlValuesGlobalName}.url.resourcesPath.replace(/^\\//,"") + "/" + "static/`
|
||||
const fixedJsCode = jsCode.replace(
|
||||
/[a-z]+\.[a-z]+\+"static\//g,
|
||||
(() => {
|
||||
switch (mode.type) {
|
||||
case "standalone":
|
||||
return `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`;
|
||||
case "external assets":
|
||||
return `"${mode.urlOrigin}${mode.urlPathname}static/`;
|
||||
}
|
||||
})()
|
||||
);
|
||||
|
||||
return { fixedJsCode };
|
||||
|
||||
}
|
||||
|
||||
export function replaceImportFromStaticInCssCode(
|
||||
export function replaceImportsInInlineCssCode(
|
||||
params: {
|
||||
cssCode: string;
|
||||
mode: Mode;
|
||||
}
|
||||
): { fixedCssCode: string; } {
|
||||
|
||||
const { cssCode, mode } = params;
|
||||
|
||||
const fixedCssCode = cssCode.replace(
|
||||
/url\((\/[^/][^)]+)\)/g,
|
||||
(...[,group])=> `url(${
|
||||
(()=>{
|
||||
switch(mode.type){
|
||||
case "standalone": return "${url.resourcesPath}/build" + group;
|
||||
case "external assets": return mode.urlOrigin + group
|
||||
}
|
||||
})()
|
||||
})`
|
||||
);
|
||||
|
||||
return { fixedCssCode };
|
||||
|
||||
}
|
||||
|
||||
export function replaceImportsInCssCode(
|
||||
params: {
|
||||
cssCode: string;
|
||||
}
|
||||
@ -32,7 +73,7 @@ export function replaceImportFromStaticInCssCode(
|
||||
|
||||
const cssGlobalsToDefine: Record<string, string> = {};
|
||||
|
||||
new Set(cssCode.match(/(url\(\/[^)]+\))/g) ?? [])
|
||||
new Set(cssCode.match(/url\(\/[^/][^)]+\)[^;}]*/g) ?? [])
|
||||
.forEach(match =>
|
||||
cssGlobalsToDefine[
|
||||
"url" + crypto
|
||||
@ -60,12 +101,13 @@ export function replaceImportFromStaticInCssCode(
|
||||
export function generateCssCodeToDefineGlobals(
|
||||
params: {
|
||||
cssGlobalsToDefine: Record<string, string>;
|
||||
urlPathname: string;
|
||||
}
|
||||
): {
|
||||
cssCodeToPrependInHead: string;
|
||||
} {
|
||||
|
||||
const { cssGlobalsToDefine } = params;
|
||||
const { cssGlobalsToDefine, urlPathname } = params;
|
||||
|
||||
return {
|
||||
"cssCodeToPrependInHead": [
|
||||
@ -73,12 +115,8 @@ export function generateCssCodeToDefineGlobals(
|
||||
...Object.keys(cssGlobalsToDefine)
|
||||
.map(cssVariableName => [
|
||||
`--${cssVariableName}:`,
|
||||
[
|
||||
"url(",
|
||||
"${url.resourcesPath}" +
|
||||
cssGlobalsToDefine[cssVariableName].match(/^url\(([^)]+)\)$/)![1],
|
||||
")"
|
||||
].join("")
|
||||
cssGlobalsToDefine[cssVariableName]
|
||||
.replace(new RegExp(`url\\(${urlPathname.replace(/\//g, "\\/")}`, "g"), "url(${url.resourcesPath}/build/")
|
||||
].join(" "))
|
||||
.map(line => ` ${line};`),
|
||||
"}"
|
||||
|
@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as fs from "fs";
|
||||
import { join as pathJoin, basename as pathBasename } from "path";
|
||||
import { keycloakThemeBuildingDirPath } from "./build-keycloak-theme";
|
||||
import child_process from "child_process";
|
||||
|
||||
if (!fs.existsSync(keycloakThemeBuildingDirPath)) {
|
||||
console.log("Error: The keycloak theme need to be build");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const url = "https://github.com/garronej/keycloak-react-theming/releases/download/v0.0.1/other_keycloak_thems.zip";
|
||||
|
||||
[
|
||||
`wget ${url}`,
|
||||
...["unzip", "rm"].map(prg => `${prg} ${pathBasename(url)}`),
|
||||
].forEach(cmd => child_process.execSync(cmd, { "cwd": pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme") }));
|
76
src/bin/generate-i18n-messages.ts
Normal file
76
src/bin/generate-i18n-messages.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import "minimal-polyfills/Object.fromEntries";
|
||||
import * as fs from "fs";
|
||||
import { join as pathJoin, relative as pathRelative } from "path";
|
||||
import { crawl } from "./tools/crawl";
|
||||
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
||||
import { builtinThemesUrl } from "./install-builtin-keycloak-themes";
|
||||
import { getProjectRoot } from "./tools/getProjectRoot";
|
||||
import * as child_process from "child_process";
|
||||
|
||||
//@ts-ignore
|
||||
const propertiesParser = require("properties-parser");
|
||||
|
||||
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
|
||||
|
||||
child_process.execSync(`rm -rf ${tmpDirPath}`);
|
||||
|
||||
downloadAndUnzip({
|
||||
"destDirPath": tmpDirPath,
|
||||
"url": builtinThemesUrl
|
||||
});
|
||||
|
||||
type Dictionary = { [idiomId: string]: string };
|
||||
|
||||
const record: { [typeOfPage: string]: { [language: string]: Dictionary } } = {};
|
||||
|
||||
process.chdir(pathJoin(tmpDirPath, "base"));
|
||||
|
||||
crawl(".").forEach(filePath => {
|
||||
|
||||
const match = filePath.match(/^([^/]+)\/messages\/messages_([^.]+)\.properties$/);
|
||||
|
||||
if (match === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [, typeOfPage, language] = match;
|
||||
|
||||
(record[typeOfPage] ??= {})[language.replace(/_/g, "-")] =
|
||||
Object.fromEntries(
|
||||
Object.entries(
|
||||
propertiesParser.parse(
|
||||
fs.readFileSync(filePath)
|
||||
.toString("utf8")
|
||||
)
|
||||
).map(([key, value]: any) => [key, value.replace(/''/g, "'")])
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
child_process.execSync(`rm -r ${tmpDirPath}`);
|
||||
|
||||
const targetDirPath = pathJoin(getProjectRoot(), "src", "lib", "i18n", "generated_messages");
|
||||
|
||||
fs.mkdirSync(targetDirPath, { "recursive": true });
|
||||
|
||||
Object.keys(record).forEach(pageType => {
|
||||
|
||||
const filePath = pathJoin(targetDirPath, `${pageType}.ts`);
|
||||
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
Buffer.from(
|
||||
[
|
||||
`//This code was automatically generated by running ${pathRelative(getProjectRoot(), __filename)}`,
|
||||
'//PLEASE DO NOT EDIT MANUALLY',
|
||||
'',
|
||||
'/* spell-checker: disable */',
|
||||
`export const messages= ${JSON.stringify(record[pageType], null, 2)} as const;`,
|
||||
'/* spell-checker: enable */'
|
||||
].join("\n"), "utf8")
|
||||
);
|
||||
|
||||
console.log(`${filePath} wrote`);
|
||||
|
||||
|
||||
});
|
18
src/bin/install-builtin-keycloak-themes.ts
Normal file
18
src/bin/install-builtin-keycloak-themes.ts
Normal file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { keycloakThemeBuildingDirPath } from "./build-keycloak-theme";
|
||||
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
||||
import { join as pathJoin } from "path";
|
||||
|
||||
export const builtinThemesUrl =
|
||||
"https://github.com/garronej/keycloakify/releases/download/v0.0.1/keycloak_11.0.3_builtin_themes.zip";
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
downloadAndUnzip({
|
||||
"url": builtinThemesUrl,
|
||||
"destDirPath": pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme")
|
||||
});
|
||||
|
||||
}
|
||||
|
34
src/bin/tools/downloadAndUnzip.ts
Normal file
34
src/bin/tools/downloadAndUnzip.ts
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
import { basename as pathBasename, join as pathJoin } from "path";
|
||||
import { execSync } from "child_process";
|
||||
import fs from "fs";
|
||||
import { transformCodebase } from "../tools/transformCodebase";
|
||||
|
||||
/** assert url ends with .zip */
|
||||
export function downloadAndUnzip(
|
||||
params: {
|
||||
url: string;
|
||||
destDirPath: string;
|
||||
}
|
||||
) {
|
||||
|
||||
const { url, destDirPath } = params;
|
||||
|
||||
const tmpDirPath = pathJoin(destDirPath, "..", "tmp_xxKdOxnEdx");
|
||||
|
||||
execSync(`rm -rf ${tmpDirPath}`);
|
||||
|
||||
fs.mkdirSync(tmpDirPath, { "recursive": true });
|
||||
|
||||
execSync(`wget ${url}`, { "cwd": tmpDirPath })
|
||||
execSync(`unzip ${pathBasename(url)}`, { "cwd": tmpDirPath });
|
||||
execSync(`rm ${pathBasename(url)}`, { "cwd": tmpDirPath });
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": tmpDirPath,
|
||||
"destDirPath": destDirPath,
|
||||
});
|
||||
|
||||
execSync(`rm -r ${tmpDirPath}`);
|
||||
|
||||
}
|
19
src/bin/tools/getProjectRoot.ts
Normal file
19
src/bin/tools/getProjectRoot.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
function getProjectRootRec(dirPath: string): string {
|
||||
if (fs.existsSync(path.join(dirPath, "package.json"))) {
|
||||
return dirPath;
|
||||
}
|
||||
return getProjectRootRec(path.join(dirPath, ".."));
|
||||
}
|
||||
|
||||
let result: string | undefined = undefined;
|
||||
|
||||
export function getProjectRoot(): string {
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return (result = getProjectRootRec(__dirname));
|
||||
}
|
8
src/bin/tools/grant-exec-perms.ts
Normal file
8
src/bin/tools/grant-exec-perms.ts
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
import { getProjectRoot } from "./getProjectRoot";
|
||||
import { join as pathJoin } from "path";
|
||||
import child_process from "child_process";
|
||||
|
||||
Object.entries<string>(require(pathJoin(getProjectRoot(), "package.json"))["bin"])
|
||||
.forEach(([, scriptPath]) => child_process.execSync(`chmod +x ${scriptPath}`, { "cwd": getProjectRoot() }));
|
||||
|
14
src/bin/tools/isInside.ts
Normal file
14
src/bin/tools/isInside.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { relative as pathRelative } from "path";
|
||||
|
||||
export function isInside(
|
||||
params: {
|
||||
dirPath: string;
|
||||
filePath: string;
|
||||
}
|
||||
) {
|
||||
|
||||
const { dirPath, filePath } = params;
|
||||
|
||||
return !pathRelative(dirPath, filePath).startsWith("..");
|
||||
|
||||
}
|
@ -3,34 +3,42 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { crawl } from "./crawl";
|
||||
import { id } from "evt/tools/typeSafety/id";
|
||||
|
||||
/** Apply a transformation function to every file of directory */
|
||||
export function transformCodebase(
|
||||
params: {
|
||||
srcDirPath: string;
|
||||
destDirPath: string;
|
||||
transformSourceCodeString: (params: {
|
||||
type TransformSourceCode =
|
||||
(params: {
|
||||
sourceCode: Buffer;
|
||||
filePath: string;
|
||||
}) => {
|
||||
modifiedSourceCode: Buffer;
|
||||
newFileName?: string;
|
||||
} | undefined;
|
||||
|
||||
/** Apply a transformation function to every file of directory */
|
||||
export function transformCodebase(
|
||||
params: {
|
||||
srcDirPath: string;
|
||||
destDirPath: string;
|
||||
transformSourceCode?: TransformSourceCode;
|
||||
}
|
||||
) {
|
||||
|
||||
const { srcDirPath, destDirPath, transformSourceCodeString } = params;
|
||||
const {
|
||||
srcDirPath,
|
||||
destDirPath,
|
||||
transformSourceCode = id<TransformSourceCode>(({ sourceCode }) => ({ "modifiedSourceCode": sourceCode }))
|
||||
} = params;
|
||||
|
||||
for (const file_relative_path of crawl(srcDirPath)) {
|
||||
|
||||
const filePath = path.join(srcDirPath, file_relative_path);
|
||||
|
||||
const transformSourceCodeStringResult = transformSourceCodeString({
|
||||
const transformSourceCodeResult = transformSourceCode({
|
||||
"sourceCode": fs.readFileSync(filePath),
|
||||
"filePath": path.join(srcDirPath, file_relative_path)
|
||||
});
|
||||
|
||||
if (transformSourceCodeStringResult === undefined) {
|
||||
if (transformSourceCodeResult === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -44,7 +52,7 @@ export function transformCodebase(
|
||||
{ "recursive": true }
|
||||
);
|
||||
|
||||
const { newFileName, modifiedSourceCode } = transformSourceCodeStringResult;
|
||||
const { newFileName, modifiedSourceCode } = transformSourceCodeResult;
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(
|
39
src/lib/components/Error.tsx
Normal file
39
src/lib/components/Error.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import { assert } from "../tools/assert";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
|
||||
export const Error = memo(({ kcContext, ...props }: { kcContext: KcContext.Error; } & KcProps) => {
|
||||
|
||||
const { msg } = useKcMessage();
|
||||
|
||||
assert(kcContext.message !== undefined);
|
||||
|
||||
const { message, client } = kcContext;
|
||||
|
||||
return (
|
||||
<Template
|
||||
{...{ kcContext, ...props }}
|
||||
displayMessage={false}
|
||||
headerNode={msg("errorTitle")}
|
||||
formNode={
|
||||
<div id="kc-error-message">
|
||||
<p className="instruction">{message.summary}</p>
|
||||
{
|
||||
client !== undefined && client.baseUrl !== undefined &&
|
||||
<p>
|
||||
<a id="backToApplication" href={client.baseUrl}>
|
||||
{msg("backToApplication")}
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
|
72
src/lib/components/Info.tsx
Normal file
72
src/lib/components/Info.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import { assert } from "../tools/assert";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
|
||||
export const Info = memo(({ kcContext, ...props }: { kcContext: KcContext.Info; } & KcProps) => {
|
||||
|
||||
const { msg } = useKcMessage();
|
||||
|
||||
assert(kcContext.message !== undefined);
|
||||
|
||||
const {
|
||||
messageHeader,
|
||||
message,
|
||||
requiredActions,
|
||||
skipLink,
|
||||
pageRedirectUri,
|
||||
actionUri,
|
||||
client
|
||||
} = kcContext;
|
||||
|
||||
return (
|
||||
<Template
|
||||
{...{ kcContext, ...props }}
|
||||
displayMessage={false}
|
||||
headerNode={
|
||||
messageHeader !== undefined ?
|
||||
<>{messageHeader}</>
|
||||
:
|
||||
<>{message.summary}</>
|
||||
}
|
||||
formNode={
|
||||
<div id="kc-info-message">
|
||||
<p className="instruction">{message.summary}
|
||||
|
||||
{
|
||||
requiredActions !== undefined &&
|
||||
<b>
|
||||
{
|
||||
requiredActions
|
||||
.map(requiredAction => msg(`requiredAction.${requiredAction}` as const))
|
||||
.join(",")
|
||||
}
|
||||
|
||||
</b>
|
||||
|
||||
}
|
||||
|
||||
</p>
|
||||
{
|
||||
!skipLink &&
|
||||
pageRedirectUri !== undefined ?
|
||||
<p><a href="${pageRedirectUri}">${(msg("backToApplication"))}</a></p>
|
||||
:
|
||||
actionUri !== undefined ?
|
||||
<p><a href="${actionUri}">${msg("proceedWithAction")}</a></p>
|
||||
:
|
||||
client.baseUrl !== undefined &&
|
||||
<p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
|
21
src/lib/components/KcApp.tsx
Normal file
21
src/lib/components/KcApp.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import { Login } from "./Login";
|
||||
import { Register } from "./Register";
|
||||
import { Info } from "./Info";
|
||||
import { Error } from "./Error";
|
||||
import { LoginResetPassword } from "./LoginResetPassword";
|
||||
import { LoginVerifyEmail } from "./LoginVerifyEmail";
|
||||
|
||||
export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContext; } & KcProps ) => {
|
||||
switch (kcContext.pageId) {
|
||||
case "login.ftl": return <Login {...{ kcContext, ...props }} />;
|
||||
case "register.ftl": return <Register {...{ kcContext, ...props }} />;
|
||||
case "info.ftl": return <Info {...{ kcContext, ...props }} />;
|
||||
case "error.ftl": return <Error {...{ kcContext, ...props }} />;
|
||||
case "login-reset-password.ftl": return <LoginResetPassword {...{ kcContext, ...props }} />;
|
||||
case "login-verify-email.ftl": return <LoginVerifyEmail {...{ kcContext, ...props }} />;
|
||||
}
|
||||
});
|
205
src/lib/components/KcProps.ts
Normal file
205
src/lib/components/KcProps.ts
Normal file
@ -0,0 +1,205 @@
|
||||
|
||||
import { allPropertiesValuesToUndefined } from "../tools/allPropertiesValuesToUndefined";
|
||||
import { doExtends } from "evt/tools/typeSafety/doExtends";
|
||||
|
||||
/** Class names can be provided as an array or separated by whitespace */
|
||||
export type KcPropsGeneric<CssClasses extends string> = { [key in CssClasses]: readonly string[] | string | undefined; };
|
||||
|
||||
export type KcTemplateClassKey =
|
||||
"stylesCommon" |
|
||||
"styles" |
|
||||
"scripts" |
|
||||
"kcHtmlClass" |
|
||||
"kcLoginClass" |
|
||||
"kcHeaderClass" |
|
||||
"kcHeaderWrapperClass" |
|
||||
"kcFormCardClass" |
|
||||
"kcFormCardAccountClass" |
|
||||
"kcFormHeaderClass" |
|
||||
"kcLocaleWrapperClass" |
|
||||
"kcContentWrapperClass" |
|
||||
"kcLabelWrapperClass" |
|
||||
"kcContentWrapperClass" |
|
||||
"kcLabelWrapperClass" |
|
||||
"kcFormGroupClass" |
|
||||
"kcResetFlowIcon" |
|
||||
"kcResetFlowIcon" |
|
||||
"kcFeedbackSuccessIcon" |
|
||||
"kcFeedbackWarningIcon" |
|
||||
"kcFeedbackErrorIcon" |
|
||||
"kcFeedbackInfoIcon" |
|
||||
"kcContentWrapperClass" |
|
||||
"kcFormSocialAccountContentClass" |
|
||||
"kcFormSocialAccountClass" |
|
||||
"kcSignUpClass" |
|
||||
"kcInfoAreaWrapperClass"
|
||||
;
|
||||
|
||||
export type KcTemplateProps = KcPropsGeneric<KcTemplateClassKey>;
|
||||
|
||||
export const defaultKcTemplateProps = {
|
||||
"stylesCommon": [
|
||||
"node_modules/patternfly/dist/css/patternfly.min.css",
|
||||
"node_modules/patternfly/dist/css/patternfly-additions.min.css",
|
||||
"lib/zocial/zocial.css"
|
||||
],
|
||||
"styles": ["css/login.css"],
|
||||
"scripts": [],
|
||||
"kcHtmlClass": ["login-pf"],
|
||||
"kcLoginClass": ["login-pf-page"],
|
||||
"kcContentWrapperClass": ["row"],
|
||||
"kcHeaderClass": ["login-pf-page-header"],
|
||||
"kcHeaderWrapperClass": [],
|
||||
"kcFormCardClass": ["card-pf"],
|
||||
"kcFormCardAccountClass": ["login-pf-accounts"],
|
||||
"kcFormSocialAccountClass": ["login-pf-social-section"],
|
||||
"kcFormSocialAccountContentClass": ["col-xs-12", "col-sm-6"],
|
||||
"kcFormHeaderClass": ["login-pf-header"],
|
||||
"kcLocaleWrapperClass": [],
|
||||
"kcFeedbackErrorIcon": ["pficon", "pficon-error-circle-o"],
|
||||
"kcFeedbackWarningIcon": ["pficon", "pficon-warning-triangle-o"],
|
||||
"kcFeedbackSuccessIcon": ["pficon", "pficon-ok"],
|
||||
"kcFeedbackInfoIcon": ["pficon", "pficon-info"],
|
||||
"kcResetFlowIcon": ["pficon", "pficon-arrow fa-2x"],
|
||||
"kcFormGroupClass": ["form-group"],
|
||||
"kcLabelWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
|
||||
"kcSignUpClass": ["login-pf-signup"],
|
||||
"kcInfoAreaWrapperClass": []
|
||||
} as const;
|
||||
|
||||
|
||||
doExtends<typeof defaultKcTemplateProps, KcTemplateProps>();
|
||||
|
||||
/** Tu use if you don't want any default */
|
||||
export const allClearKcTemplateProps =
|
||||
allPropertiesValuesToUndefined(defaultKcTemplateProps);
|
||||
|
||||
doExtends<typeof allClearKcTemplateProps, KcTemplateProps>();
|
||||
|
||||
export type KcProps = KcPropsGeneric<
|
||||
KcTemplateClassKey |
|
||||
"kcLogoLink" |
|
||||
"kcLogoClass" |
|
||||
"kcContainerClass" |
|
||||
"kcContentClass" |
|
||||
"kcFeedbackAreaClass" |
|
||||
"kcLocaleClass" |
|
||||
"kcAlertIconClasserror" |
|
||||
"kcFormAreaClass" |
|
||||
"kcFormSocialAccountListClass" |
|
||||
"kcFormSocialAccountDoubleListClass" |
|
||||
"kcFormSocialAccountListLinkClass" |
|
||||
"kcWebAuthnKeyIcon" |
|
||||
"kcFormClass" |
|
||||
"kcFormGroupErrorClass" |
|
||||
"kcLabelClass" |
|
||||
"kcInputClass" |
|
||||
"kcInputWrapperClass" |
|
||||
"kcFormOptionsClass" |
|
||||
"kcFormButtonsClass" |
|
||||
"kcFormSettingClass" |
|
||||
"kcTextareaClass" |
|
||||
"kcInfoAreaClass" |
|
||||
"kcButtonClass" |
|
||||
"kcButtonPrimaryClass" |
|
||||
"kcButtonDefaultClass" |
|
||||
"kcButtonLargeClass" |
|
||||
"kcButtonBlockClass" |
|
||||
"kcInputLargeClass" |
|
||||
"kcSrOnlyClass" |
|
||||
"kcSelectAuthListClass" |
|
||||
"kcSelectAuthListItemClass" |
|
||||
"kcSelectAuthListItemInfoClass" |
|
||||
"kcSelectAuthListItemLeftClass" |
|
||||
"kcSelectAuthListItemBodyClass" |
|
||||
"kcSelectAuthListItemDescriptionClass" |
|
||||
"kcSelectAuthListItemHeadingClass" |
|
||||
"kcSelectAuthListItemHelpTextClass" |
|
||||
"kcAuthenticatorDefaultClass" |
|
||||
"kcAuthenticatorPasswordClass" |
|
||||
"kcAuthenticatorOTPClass" |
|
||||
"kcAuthenticatorWebAuthnClass" |
|
||||
"kcAuthenticatorWebAuthnPasswordlessClass" |
|
||||
"kcSelectOTPListClass" |
|
||||
"kcSelectOTPListItemClass" |
|
||||
"kcAuthenticatorOtpCircleClass" |
|
||||
"kcSelectOTPItemHeadingClass" |
|
||||
"kcFormOptionsWrapperClass"
|
||||
>;
|
||||
|
||||
export const defaultKcProps = {
|
||||
...defaultKcTemplateProps,
|
||||
"kcLogoLink": "http://www.keycloak.org",
|
||||
"kcLogoClass": "login-pf-brand",
|
||||
"kcContainerClass": "container-fluid",
|
||||
"kcContentClass": ["col-sm-8", "col-sm-offset-2", "col-md-6", "col-md-offset-3", "col-lg-6", "col-lg-offset-3"],
|
||||
"kcFeedbackAreaClass": ["col-md-12"],
|
||||
"kcLocaleClass": ["col-xs-12", "col-sm-1"],
|
||||
"kcAlertIconClasserror": ["pficon", "pficon-error-circle-o"],
|
||||
|
||||
"kcFormAreaClass": ["col-sm-10", "col-sm-offset-1", "col-md-8", "col-md-offset-2", "col-lg-8", "col-lg-offset-2"],
|
||||
"kcFormSocialAccountListClass": ["login-pf-social", "list-unstyled", "login-pf-social-all"],
|
||||
"kcFormSocialAccountDoubleListClass": ["login-pf-social-double-col"],
|
||||
"kcFormSocialAccountListLinkClass": ["login-pf-social-link"],
|
||||
"kcWebAuthnKeyIcon": ["pficon", "pficon-key"],
|
||||
|
||||
"kcFormClass": ["form-horizontal"],
|
||||
"kcFormGroupErrorClass": ["has-error"],
|
||||
"kcLabelClass": ["control-label"],
|
||||
"kcInputClass": ["form-control"],
|
||||
"kcInputWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
|
||||
"kcFormOptionsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
|
||||
"kcFormButtonsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
|
||||
"kcFormSettingClass": ["login-pf-settings"],
|
||||
"kcTextareaClass": ["form-control"],
|
||||
|
||||
"kcInfoAreaClass": ["col-xs-12", "col-sm-4", "col-md-4", "col-lg-5", "details"],
|
||||
|
||||
// css classes for form buttons main class used for all buttons
|
||||
"kcButtonClass": ["btn"],
|
||||
// classes defining priority of the button - primary or default (there is typically only one priority button for the form)
|
||||
"kcButtonPrimaryClass": ["btn-primary"],
|
||||
"kcButtonDefaultClass": ["btn-default"],
|
||||
// classes defining size of the button
|
||||
"kcButtonLargeClass": ["btn-lg"],
|
||||
"kcButtonBlockClass": ["btn-block"],
|
||||
|
||||
// css classes for input
|
||||
"kcInputLargeClass": ["input-lg"],
|
||||
|
||||
// css classes for form accessability
|
||||
"kcSrOnlyClass": ["sr-only"],
|
||||
|
||||
// css classes for select-authenticator form
|
||||
"kcSelectAuthListClass": ["list-group", "list-view-pf"],
|
||||
"kcSelectAuthListItemClass": ["list-group-item", "list-view-pf-stacked"],
|
||||
"kcSelectAuthListItemInfoClass": ["list-view-pf-main-info"],
|
||||
"kcSelectAuthListItemLeftClass": ["list-view-pf-left"],
|
||||
"kcSelectAuthListItemBodyClass": ["list-view-pf-body"],
|
||||
"kcSelectAuthListItemDescriptionClass": ["list-view-pf-description"],
|
||||
"kcSelectAuthListItemHeadingClass": ["list-group-item-heading"],
|
||||
"kcSelectAuthListItemHelpTextClass": ["list-group-item-text"],
|
||||
|
||||
// css classes for the authenticators
|
||||
"kcAuthenticatorDefaultClass": ["fa", "list-view-pf-icon-lg"],
|
||||
"kcAuthenticatorPasswordClass": ["fa", "fa-unlock list-view-pf-icon-lg"],
|
||||
"kcAuthenticatorOTPClass": ["fa", "fa-mobile", "list-view-pf-icon-lg"],
|
||||
"kcAuthenticatorWebAuthnClass": ["fa", "fa-key", "list-view-pf-icon-lg"],
|
||||
"kcAuthenticatorWebAuthnPasswordlessClass": ["fa", "fa-key", "list-view-pf-icon-lg"],
|
||||
|
||||
//css classes for the OTP Login Form
|
||||
"kcSelectOTPListClass": ["card-pf", "card-pf-view", "card-pf-view-select", "card-pf-view-single-select"],
|
||||
"kcSelectOTPListItemClass": ["card-pf-body", "card-pf-top-element"],
|
||||
"kcAuthenticatorOtpCircleClass": ["fa", "fa-mobile", "card-pf-icon-circle"],
|
||||
"kcSelectOTPItemHeadingClass": ["card-pf-title", "text-center"],
|
||||
"kcFormOptionsWrapperClass": []
|
||||
} as const;
|
||||
|
||||
doExtends<typeof defaultKcProps, KcProps>();
|
||||
|
||||
/** Tu use if you don't want any default */
|
||||
export const allClearKcProps =
|
||||
allPropertiesValuesToUndefined(defaultKcProps);
|
||||
|
||||
doExtends<typeof allClearKcProps, KcProps>();
|
||||
|
154
src/lib/components/Login.tsx
Normal file
154
src/lib/components/Login.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
|
||||
import { useState, memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { cx } from "tss-react";
|
||||
import { useConstCallback } from "powerhooks";
|
||||
|
||||
export const Login = memo(({ kcContext, ...props }: { kcContext: KcContext.Login; } & KcProps) => {
|
||||
|
||||
const { msg, msgStr } = useKcMessage();
|
||||
|
||||
const {
|
||||
social, realm, url,
|
||||
usernameEditDisabled, login,
|
||||
auth, registrationDisabled
|
||||
} = kcContext;
|
||||
|
||||
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
|
||||
|
||||
const onSubmit = useConstCallback(() =>
|
||||
(setIsLoginButtonDisabled(true), true)
|
||||
);
|
||||
|
||||
return (
|
||||
<Template
|
||||
{...{ kcContext, ...props }}
|
||||
displayInfo={social.displayInfo}
|
||||
displayWide={realm.password && social.providers !== undefined}
|
||||
headerNode={msg("doLogIn")}
|
||||
formNode={
|
||||
<div
|
||||
id="kc-form"
|
||||
className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)}
|
||||
>
|
||||
<div
|
||||
id="kc-form-wrapper"
|
||||
className={cx(realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}
|
||||
>
|
||||
{
|
||||
realm.password &&
|
||||
(
|
||||
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
|
||||
<div className={cx(props.kcFormGroupClass)}>
|
||||
<label htmlFor="username" className={cx(props.kcLabelClass)}>
|
||||
{
|
||||
!realm.loginWithEmailAllowed ?
|
||||
msg("username")
|
||||
:
|
||||
(
|
||||
!realm.registrationEmailAsUsername ?
|
||||
msg("usernameOrEmail") :
|
||||
msg("email")
|
||||
)
|
||||
}
|
||||
</label>
|
||||
<input
|
||||
tabIndex={1}
|
||||
id="username"
|
||||
className={cx(props.kcInputClass)}
|
||||
name="username"
|
||||
defaultValue={login.username ?? ''}
|
||||
type="text"
|
||||
{...(usernameEditDisabled ? { "disabled": true } : { "autoFocus": true, "autoComplete": "off" })}
|
||||
/>
|
||||
</div>
|
||||
<div className={cx(props.kcFormGroupClass)}>
|
||||
<label htmlFor="password" className={cx(props.kcLabelClass)}>
|
||||
{msg("password")}
|
||||
</label>
|
||||
<input tabIndex={2} id="password" className={cx(props.kcInputClass)} name="password" type="password" autoComplete="off" />
|
||||
</div>
|
||||
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
|
||||
<div id="kc-form-options">
|
||||
{
|
||||
(
|
||||
realm.rememberMe &&
|
||||
!usernameEditDisabled
|
||||
) &&
|
||||
<div className="checkbox">
|
||||
<label>
|
||||
<input tabIndex={3} id="rememberMe" name="rememberMe" type="checkbox" {...(login.rememberMe ? { "checked": true } : {})} />
|
||||
{msg("rememberMe")}
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
||||
{
|
||||
realm.resetPasswordAllowed &&
|
||||
<span>
|
||||
<a tabIndex={5} href={url.loginResetCredentialsUrl}>{msg("doForgotPassword")}</a>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
|
||||
<input
|
||||
type="hidden"
|
||||
id="id-hidden-input"
|
||||
name="credentialId"
|
||||
{...(auth?.selectedCredential !== undefined ? { "value": auth.selectedCredential } : {})}
|
||||
/>
|
||||
<input
|
||||
tabIndex={4}
|
||||
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} name="login" id="kc-login" type="submit"
|
||||
value={msgStr("doLogIn")}
|
||||
disabled={isLoginButtonDisabled}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
(realm.password && social.providers !== undefined) &&
|
||||
<div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}>
|
||||
<ul className={cx(props.kcFormSocialAccountListClass, social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass)}>
|
||||
{
|
||||
social.providers.map(p =>
|
||||
<li className={cx(props.kcFormSocialAccountListLinkClass)}>
|
||||
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
|
||||
<span>{p.displayName}</span>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
infoNode={
|
||||
(
|
||||
realm.password &&
|
||||
realm.registrationAllowed &&
|
||||
!registrationDisabled
|
||||
) &&
|
||||
<div id="kc-registration">
|
||||
<span>
|
||||
{msg("noAccount")}
|
||||
<a tabIndex={6} href={url.registrationUrl}>
|
||||
{msg("doRegister")}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
|
80
src/lib/components/LoginResetPassword.tsx
Normal file
80
src/lib/components/LoginResetPassword.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { cx } from "tss-react";
|
||||
|
||||
export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: KcContext.LoginResetPassword; } & KcProps) => {
|
||||
|
||||
const { msg, msgStr } = useKcMessage();
|
||||
|
||||
const {
|
||||
url,
|
||||
realm,
|
||||
auth
|
||||
} = kcContext;
|
||||
|
||||
return (
|
||||
<Template
|
||||
{...{ kcContext, ...props }}
|
||||
displayMessage={false}
|
||||
headerNode={msg("emailForgotTitle")}
|
||||
formNode={
|
||||
<form id="kc-reset-password-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
||||
<div className={cx(props.kcFormGroupClass)}>
|
||||
<div className={cx(props.kcLabelWrapperClass)}>
|
||||
<label htmlFor="username" className={cx(props.kcLabelClass)}>
|
||||
{
|
||||
!realm.loginWithEmailAllowed ?
|
||||
msg("username")
|
||||
:
|
||||
!realm.registrationEmailAsUsername ?
|
||||
msg("usernameOrEmail") :
|
||||
msg("email")
|
||||
}
|
||||
</label>
|
||||
</div>
|
||||
<div className={cx(props.kcInputWrapperClass)}>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
className={cx(props.kcInputClass)}
|
||||
autoFocus
|
||||
defaultValue={
|
||||
auth !== undefined && auth.showUsername ?
|
||||
auth.attemptedUsername : undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
|
||||
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
||||
<span>
|
||||
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||
<input
|
||||
className={cx(
|
||||
props.kcButtonClass, props.kcButtonPrimaryClass,
|
||||
props.kcButtonBlockClass, props.kcButtonLargeClass
|
||||
)}
|
||||
type="submit"
|
||||
value={msgStr("doSubmit")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
infoNode={msg("emailInstruction")}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
|
38
src/lib/components/LoginVerifyEmail.tsx
Normal file
38
src/lib/components/LoginVerifyEmail.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
|
||||
export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcContext.LoginVerifyEmail; } & KcProps) => {
|
||||
|
||||
const { msg } = useKcMessage();
|
||||
|
||||
const {
|
||||
url
|
||||
} = kcContext;
|
||||
|
||||
return (
|
||||
<Template
|
||||
{...{ kcContext, ...props }}
|
||||
displayMessage={false}
|
||||
headerNode={msg("emailVerifyTitle")}
|
||||
formNode={
|
||||
<>
|
||||
<p className="instruction">
|
||||
{msg("emailVerifyInstruction1")}
|
||||
</p>
|
||||
<p className="instruction">
|
||||
{msg("emailVerifyInstruction2")}
|
||||
<a href={url.loginAction}>{msg("doClickHere")}</a>
|
||||
{msg("emailVerifyInstruction3")}
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
|
123
src/lib/components/Register.tsx
Normal file
123
src/lib/components/Register.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { cx } from "tss-react";
|
||||
|
||||
export const Register = memo(({ kcContext, ...props }: { kcContext: KcContext.Register; } & KcProps) => {
|
||||
|
||||
const { msg, msgStr } = useKcMessage();
|
||||
|
||||
const {
|
||||
url,
|
||||
messagesPerField,
|
||||
register,
|
||||
realm,
|
||||
passwordRequired,
|
||||
recaptchaRequired,
|
||||
recaptchaSiteKey
|
||||
} = kcContext;
|
||||
|
||||
return (
|
||||
<Template
|
||||
{...{ kcContext, ...props }}
|
||||
headerNode={msg("registerTitle")}
|
||||
formNode={
|
||||
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
|
||||
|
||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('firstName', props.kcFormGroupErrorClass))}>
|
||||
<div className={cx(props.kcLabelWrapperClass)}>
|
||||
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>{msg("firstName")}</label>
|
||||
</div>
|
||||
<div className={cx(props.kcInputWrapperClass)}>
|
||||
<input type="text" id="firstName" className={cx(props.kcInputClass)} name="firstName"
|
||||
defaultValue={register.formData.firstName ?? ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
|
||||
<div className={cx(props.kcLabelWrapperClass)}>
|
||||
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>{msg("lastName")}</label>
|
||||
</div>
|
||||
<div className={cx(props.kcInputWrapperClass)}>
|
||||
<input type="text" id="lastName" className={cx(props.kcInputClass)} name="lastName"
|
||||
defaultValue={register.formData.lastName ?? ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('email', props.kcFormGroupErrorClass))}>
|
||||
<div className={cx(props.kcLabelWrapperClass)}>
|
||||
<label htmlFor="email" className={cx(props.kcLabelClass)}>{msg("email")}</label>
|
||||
</div>
|
||||
<div className={cx(props.kcInputWrapperClass)}>
|
||||
<input type="text" id="email" className={cx(props.kcInputClass)} name="email"
|
||||
defaultValue={register.formData.email ?? ""} autoComplete="email"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
!realm.registrationEmailAsUsername &&
|
||||
|
||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('username', props.kcFormGroupErrorClass))}>
|
||||
<div className={cx(props.kcLabelWrapperClass)}>
|
||||
<label htmlFor="username" className={cx(props.kcLabelClass)}>{msg("username")}</label>
|
||||
</div>
|
||||
<div className={cx(props.kcInputWrapperClass)}>
|
||||
<input type="text" id="username" className={cx(props.kcInputClass)} name="username"
|
||||
defaultValue={register.formData.username ?? ""} autoComplete="username" />
|
||||
</div>
|
||||
</div >
|
||||
|
||||
}
|
||||
{
|
||||
passwordRequired &&
|
||||
<>
|
||||
|
||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
|
||||
<div className={cx(props.kcLabelWrapperClass)}>
|
||||
<label htmlFor="password" className={cx(props.kcLabelClass)}>{msg("password")}</label>
|
||||
</div>
|
||||
<div className={cx(props.kcInputWrapperClass)}>
|
||||
<input type="password" id="password" className={cx(props.kcInputClass)} name="password" autoComplete="new-password" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}>
|
||||
<div className={cx(props.kcLabelWrapperClass)}>
|
||||
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>{msg("passwordConfirm")}</label>
|
||||
</div>
|
||||
<div className={cx(props.kcInputWrapperClass)}>
|
||||
<input type="password" id="password-confirm" className={cx(props.kcInputClass)} name="password-confirm" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
}
|
||||
{
|
||||
recaptchaRequired &&
|
||||
<div className="form-group">
|
||||
<div className={cx(props.kcInputWrapperClass)}>
|
||||
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className={cx(props.kcFormGroupClass)}>
|
||||
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
||||
<span><a href={url.loginUrl}>{msg("backToLogin")}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||
<input className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} type="submit"
|
||||
value={msgStr("doRegister")} />
|
||||
</div>
|
||||
</div>
|
||||
</form >
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
306
src/lib/components/Template.tsx
Normal file
306
src/lib/components/Template.tsx
Normal file
@ -0,0 +1,306 @@
|
||||
|
||||
import { useReducer, useEffect, memo } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import { assert } from "../tools/assert";
|
||||
import { cx } from "tss-react";
|
||||
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
|
||||
import { getBestMatchAmongKcLanguageTag } from "../i18n/KcLanguageTag";
|
||||
import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
|
||||
import { useCallbackFactory } from "powerhooks";
|
||||
import { appendHead } from "../tools/appendHead";
|
||||
import { join as pathJoin } from "path";
|
||||
import { useConstCallback } from "powerhooks";
|
||||
import type { KcTemplateProps } from "./KcProps";
|
||||
|
||||
export type TemplateProps = {
|
||||
displayInfo?: boolean;
|
||||
displayMessage?: boolean;
|
||||
displayRequiredFields?: boolean;
|
||||
displayWide?: boolean;
|
||||
showAnotherWayIfPresent?: boolean;
|
||||
headerNode: ReactNode;
|
||||
showUsernameNode?: ReactNode;
|
||||
formNode: ReactNode;
|
||||
infoNode?: ReactNode;
|
||||
} & { kcContext: KcContext.Template; } & KcTemplateProps;
|
||||
|
||||
export const Template = memo((props: TemplateProps) => {
|
||||
|
||||
const {
|
||||
displayInfo = false,
|
||||
displayMessage = true,
|
||||
displayRequiredFields = false,
|
||||
displayWide = false,
|
||||
showAnotherWayIfPresent = true,
|
||||
headerNode,
|
||||
showUsernameNode = null,
|
||||
formNode,
|
||||
infoNode = null,
|
||||
kcContext
|
||||
} = props;
|
||||
|
||||
useEffect(() => { console.log("Rendering this page with react using keycloakify") }, []);
|
||||
|
||||
const { msg } = useKcMessage();
|
||||
|
||||
const { kcLanguageTag, setKcLanguageTag } = useKcLanguageTag();
|
||||
|
||||
|
||||
const onChangeLanguageClickFactory = useCallbackFactory(
|
||||
([languageTag]: [KcLanguageTag]) =>
|
||||
setKcLanguageTag(languageTag)
|
||||
);
|
||||
|
||||
const onTryAnotherWayClick = useConstCallback(() =>
|
||||
(document.forms["kc-select-try-another-way-form" as never].submit(), false)
|
||||
);
|
||||
|
||||
const {
|
||||
realm, locale, auth,
|
||||
url, message, isAppInitiatedAction
|
||||
} = kcContext;
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (!realm.internationalizationEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(locale !== undefined);
|
||||
|
||||
if (kcLanguageTag === getBestMatchAmongKcLanguageTag(locale.current)) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href =
|
||||
locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag)!.url;
|
||||
|
||||
}, [kcLanguageTag]);
|
||||
|
||||
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
let isUnmounted = false;
|
||||
const cleanups: (() => void)[] = [];
|
||||
|
||||
const toArr = (x: string | readonly string[] | undefined) =>
|
||||
typeof x === "string" ? x.split(" ") : x ?? [];
|
||||
|
||||
Promise.all(
|
||||
[
|
||||
...toArr(props.stylesCommon).map(relativePath => pathJoin(url.resourcesCommonPath, relativePath)),
|
||||
...toArr(props.styles).map(relativePath => pathJoin(url.resourcesPath, relativePath))
|
||||
].map(href => appendHead({
|
||||
"type": "css",
|
||||
href
|
||||
}))).then(() => {
|
||||
|
||||
if (isUnmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
setExtraCssLoaded();
|
||||
|
||||
});
|
||||
|
||||
toArr(props.scripts).forEach(
|
||||
relativePath => appendHead({
|
||||
"type": "javascript",
|
||||
"src": pathJoin(url.resourcesPath, relativePath)
|
||||
})
|
||||
);
|
||||
|
||||
if (props.kcHtmlClass !== undefined) {
|
||||
|
||||
const htmlClassList =
|
||||
document.getElementsByTagName("html")[0]
|
||||
.classList;
|
||||
|
||||
const tokens = cx(props.kcHtmlClass).split(" ")
|
||||
|
||||
htmlClassList.add(...tokens);
|
||||
|
||||
cleanups.push(() => htmlClassList.remove(...tokens));
|
||||
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
||||
isUnmounted = true;
|
||||
|
||||
cleanups.forEach(f => f());
|
||||
|
||||
};
|
||||
|
||||
}, [props.kcHtmlClass]);
|
||||
|
||||
if (!isExtraCssLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(props.kcLoginClass)}>
|
||||
|
||||
<div id="kc-header" className={cx(props.kcHeaderClass)}>
|
||||
<div id="kc-header-wrapper" className={cx(props.kcHeaderWrapperClass)}>
|
||||
{msg("loginTitleHtml", realm.displayNameHtml)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(props.kcFormCardClass, displayWide && props.kcFormCardAccountClass)}>
|
||||
<header className={cx(props.kcFormHeaderClass)}>
|
||||
{
|
||||
(
|
||||
realm.internationalizationEnabled &&
|
||||
(assert(locale !== undefined), true) &&
|
||||
locale.supported.length > 1
|
||||
) &&
|
||||
<div id="kc-locale">
|
||||
<div id="kc-locale-wrapper" className={cx(props.kcLocaleWrapperClass)}>
|
||||
<div className="kc-dropdown" id="kc-locale-dropdown">
|
||||
<a href="#" id="kc-current-locale-link">
|
||||
{getKcLanguageTagLabel(kcLanguageTag)}
|
||||
</a>
|
||||
<ul>
|
||||
{
|
||||
locale.supported.map(
|
||||
({ languageTag }) =>
|
||||
<li key={languageTag} className="kc-dropdown-item">
|
||||
<a href="#" onClick={onChangeLanguageClickFactory(languageTag)}>
|
||||
{getKcLanguageTagLabel(languageTag)}
|
||||
</a>
|
||||
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
{
|
||||
!(
|
||||
auth !== undefined &&
|
||||
auth.showUsername &&
|
||||
!auth.showResetCredentials
|
||||
) ?
|
||||
(
|
||||
displayRequiredFields ?
|
||||
(
|
||||
|
||||
<div className={cx(props.kcContentWrapperClass)}>
|
||||
<div className={cx(props.kcLabelWrapperClass, "subtitle")}>
|
||||
<span className="subtitle">
|
||||
<span className="required">*</span>
|
||||
{msg("requiredFields")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-md-10">
|
||||
<h1 id="kc-page-title">{headerNode}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
:
|
||||
(
|
||||
|
||||
<h1 id="kc-page-title">{headerNode}</h1>
|
||||
|
||||
)
|
||||
) : (
|
||||
displayRequiredFields ? (
|
||||
<div className={cx(props.kcContentWrapperClass)}>
|
||||
<div className={cx(props.kcLabelWrapperClass, "subtitle")}>
|
||||
<span className="subtitle"><span className="required">*</span> {msg("requiredFields")}</span>
|
||||
</div>
|
||||
<div className="col-md-10">
|
||||
{showUsernameNode}
|
||||
<div className={cx(props.kcFormGroupClass)}>
|
||||
<div id="kc-username">
|
||||
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
|
||||
<a id="reset-login" href={url.loginRestartFlowUrl}>
|
||||
<div className="kc-login-tooltip">
|
||||
<i className={cx(props.kcResetFlowIcon)}></i>
|
||||
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{showUsernameNode}
|
||||
<div className={cx(props.kcFormGroupClass)}>
|
||||
<div id="kc-username">
|
||||
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
|
||||
<a id="reset-login" href={url.loginRestartFlowUrl}>
|
||||
<div className="kc-login-tooltip">
|
||||
<i className={cx(props.kcResetFlowIcon)}></i>
|
||||
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
)
|
||||
}
|
||||
</header>
|
||||
<div id="kc-content">
|
||||
<div id="kc-content-wrapper">
|
||||
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
|
||||
{
|
||||
(
|
||||
displayMessage &&
|
||||
message !== undefined &&
|
||||
(
|
||||
message.type !== "warning" ||
|
||||
!isAppInitiatedAction
|
||||
)
|
||||
) &&
|
||||
<div className={cx("alert", `alert-${message.type}`)}>
|
||||
{message.type === "success" && <span className={cx(props.kcFeedbackSuccessIcon)}></span>}
|
||||
{message.type === "warning" && <span className={cx(props.kcFeedbackWarningIcon)}></span>}
|
||||
{message.type === "error" && <span className={cx(props.kcFeedbackErrorIcon)}></span>}
|
||||
{message.type === "info" && <span className={cx(props.kcFeedbackInfoIcon)}></span>}
|
||||
<span className="kc-feedback-text">{message.summary}</span>
|
||||
</div>
|
||||
}
|
||||
{formNode}
|
||||
{
|
||||
(
|
||||
auth !== undefined &&
|
||||
auth.showTryAnotherWayLink &&
|
||||
showAnotherWayIfPresent
|
||||
) &&
|
||||
|
||||
<form id="kc-select-try-another-way-form" action={url.loginAction} method="post" className={cx(displayWide && props.kcContentWrapperClass)} >
|
||||
<div className={cx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])} >
|
||||
<div className={cx(props.kcFormGroupClass)}>
|
||||
<input type="hidden" name="tryAnotherWay" value="on" />
|
||||
<a href="#" id="try-another-way" onClick={onTryAnotherWayClick}>{msg("doTryAnotherWay")}</a>
|
||||
</div>
|
||||
</div >
|
||||
</form>
|
||||
}
|
||||
{
|
||||
displayInfo &&
|
||||
|
||||
<div id="kc-info" className={cx(props.kcSignUpClass)}>
|
||||
<div id="kc-info-wrapper" className={cx(props.kcInfoAreaWrapperClass)}>
|
||||
{infoNode}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
72
src/lib/i18n/KcLanguageTag.ts
Normal file
72
src/lib/i18n/KcLanguageTag.ts
Normal file
@ -0,0 +1,72 @@
|
||||
|
||||
import { objectKeys } from "evt/tools/typeSafety/objectKeys";
|
||||
import { messages } from "./generated_messages/login";
|
||||
|
||||
export type KcLanguageTag = keyof typeof messages;
|
||||
|
||||
export type LanguageLabel =
|
||||
/* spell-checker: disable */
|
||||
"Deutsch" | "Norsk" | "Русский" | "Svenska" | "Português (Brasil)" | "Lietuvių" |
|
||||
"English" | "Italiano" | "Français" | "中文简体" | "Español" | "Čeština" | "日本語" |
|
||||
"Slovenčina" | "Polski" | "Català" | "Nederlands" | "Türkçe";
|
||||
/* spell-checker: enable */
|
||||
|
||||
export function getKcLanguageTagLabel(language: KcLanguageTag): LanguageLabel {
|
||||
|
||||
switch (language) {
|
||||
/* spell-checker: disable */
|
||||
case "es": return "Español";
|
||||
case "it": return "Italiano";
|
||||
case "fr": return "Français";
|
||||
case "ca": return "Català";
|
||||
case "en": return "English";
|
||||
case "de": return "Deutsch";
|
||||
case "no": return "Norsk";
|
||||
case "pt-BR": return "Português (Brasil)";
|
||||
case "ru": return "Русский";
|
||||
case "sk": return "Slovenčina";
|
||||
case "ja": return "日本語";
|
||||
case "pl": return "Polski";
|
||||
case "zh-CN": return "中文简体"
|
||||
case "sv": return "Svenska";
|
||||
case "lt": return "Lietuvių";
|
||||
case "cs": return "Čeština";
|
||||
case "nl": return "Nederlands";
|
||||
case "tr": return "Türkçe";
|
||||
/* spell-checker: enable */
|
||||
}
|
||||
|
||||
return language;
|
||||
|
||||
}
|
||||
|
||||
const availableLanguages = objectKeys(messages);
|
||||
|
||||
/**
|
||||
* Pass in "fr-FR" or "français" for example, it will return the AvailableLanguage
|
||||
* it corresponds to: "fr".
|
||||
* If there is no reasonable match it's guessed from navigator.language.
|
||||
* If still no matches "en" is returned.
|
||||
*/
|
||||
export function getBestMatchAmongKcLanguageTag(
|
||||
languageLike: string
|
||||
): KcLanguageTag {
|
||||
|
||||
const iso2LanguageLike = languageLike.split("-")[0].toLowerCase();
|
||||
|
||||
const kcLanguageTag = availableLanguages.find(language =>
|
||||
language.toLowerCase().includes(iso2LanguageLike) ||
|
||||
getKcLanguageTagLabel(language).toLocaleLowerCase() === languageLike.toLocaleLowerCase()
|
||||
);
|
||||
|
||||
if (kcLanguageTag !== undefined) {
|
||||
return kcLanguageTag;
|
||||
}
|
||||
|
||||
if (languageLike !== navigator.language) {
|
||||
return getBestMatchAmongKcLanguageTag(navigator.language);
|
||||
}
|
||||
|
||||
return "en";
|
||||
}
|
||||
|
3064
src/lib/i18n/generated_messages/account.ts
Normal file
3064
src/lib/i18n/generated_messages/account.ts
Normal file
File diff suppressed because it is too large
Load Diff
244
src/lib/i18n/generated_messages/admin.ts
Normal file
244
src/lib/i18n/generated_messages/admin.ts
Normal file
@ -0,0 +1,244 @@
|
||||
//This code was automatically generated by running dist/bin/generate-i18n-messages.js
|
||||
//PLEASE DO NOT EDIT MANUALLY
|
||||
|
||||
/* spell-checker: disable */
|
||||
export const messages= {
|
||||
"ca": {
|
||||
"invalidPasswordHistoryMessage": "Contrasenya incorrecta: no pot ser igual a cap de les últimes {0} contrasenyes.",
|
||||
"invalidPasswordMinDigitsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.",
|
||||
"invalidPasswordMinLengthMessage": "Contrasenya incorrecta: longitud mínima {0}.",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} lletres minúscules.",
|
||||
"invalidPasswordMinSpecialCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} caràcters especials.",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} lletres majúscules.",
|
||||
"invalidPasswordNotUsernameMessage": "Contrasenya incorrecta: no pot ser igual al nom d'usuari.",
|
||||
"invalidPasswordRegexPatternMessage": "Contrasenya incorrecta: no compleix l'expressió regular."
|
||||
},
|
||||
"de": {
|
||||
"invalidPasswordMinLengthMessage": "Ungültiges Passwort: muss mindestens {0} Zeichen beinhalten.",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "Ungültiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.",
|
||||
"invalidPasswordMinDigitsMessage": "Ungültiges Passwort: muss mindestens {0} Ziffern beinhalten.",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "Ungültiges Passwort: muss mindestens {0} Großbuchstaben beinhalten.",
|
||||
"invalidPasswordMinSpecialCharsMessage": "Ungültiges Passwort: muss mindestens {0} Sonderzeichen beinhalten.",
|
||||
"invalidPasswordNotUsernameMessage": "Ungültiges Passwort: darf nicht identisch mit dem Benutzernamen sein.",
|
||||
"invalidPasswordRegexPatternMessage": "Ungültiges Passwort: stimmt nicht mit Regex-Muster überein.",
|
||||
"invalidPasswordHistoryMessage": "Ungültiges Passwort: darf nicht identisch mit einem der letzten {0} Passwörter sein.",
|
||||
"invalidPasswordBlacklistedMessage": "Ungültiges Passwort: Passwort ist zu bekannt und auf der schwarzen Liste.",
|
||||
"invalidPasswordGenericMessage": "Ungültiges Passwort: neues Passwort erfüllt die Passwort-Anforderungen nicht."
|
||||
},
|
||||
"en": {
|
||||
"invalidPasswordMinLengthMessage": "Invalid password: minimum length {0}.",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "Invalid password: must contain at least {0} lower case characters.",
|
||||
"invalidPasswordMinDigitsMessage": "Invalid password: must contain at least {0} numerical digits.",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "Invalid password: must contain at least {0} upper case characters.",
|
||||
"invalidPasswordMinSpecialCharsMessage": "Invalid password: must contain at least {0} special characters.",
|
||||
"invalidPasswordNotUsernameMessage": "Invalid password: must not be equal to the username.",
|
||||
"invalidPasswordRegexPatternMessage": "Invalid password: fails to match regex pattern(s).",
|
||||
"invalidPasswordHistoryMessage": "Invalid password: must not be equal to any of last {0} passwords.",
|
||||
"invalidPasswordBlacklistedMessage": "Invalid password: password is blacklisted.",
|
||||
"invalidPasswordGenericMessage": "Invalid password: new password does not match password policies.",
|
||||
"ldapErrorInvalidCustomFilter": "Custom configured LDAP filter does not start with \"(\" or does not end with \")\".",
|
||||
"ldapErrorConnectionTimeoutNotNumber": "Connection Timeout must be a number",
|
||||
"ldapErrorReadTimeoutNotNumber": "Read Timeout must be a number",
|
||||
"ldapErrorMissingClientId": "Client ID needs to be provided in config when Realm Roles Mapping is not used.",
|
||||
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Not possible to preserve group inheritance and use UID membership type together.",
|
||||
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Can not set write only when LDAP provider mode is not WRITABLE",
|
||||
"ldapErrorCantWriteOnlyAndReadOnly": "Can not set write-only and read-only together",
|
||||
"ldapErrorCantEnableStartTlsAndConnectionPooling": "Can not enable both StartTLS and connection pooling.",
|
||||
"ldapErrorCantEnableUnsyncedAndImportOff": "Can not disable Importing users when LDAP provider mode is UNSYNCED",
|
||||
"ldapErrorMissingGroupsPathGroup": "Groups path group does not exist - please create the group on specified path first",
|
||||
"clientRedirectURIsFragmentError": "Redirect URIs must not contain an URI fragment",
|
||||
"clientRootURLFragmentError": "Root URL must not contain an URL fragment",
|
||||
"clientRootURLIllegalSchemeError": "Root URL uses an illegal scheme",
|
||||
"clientBaseURLIllegalSchemeError": "Base URL uses an illegal scheme",
|
||||
"clientRedirectURIsIllegalSchemeError": "A redirect URI uses an illegal scheme",
|
||||
"clientBaseURLInvalid": "Base URL is not a valid URL",
|
||||
"clientRootURLInvalid": "Root URL is not a valid URL",
|
||||
"clientRedirectURIsInvalid": "A redirect URI is not a valid URI",
|
||||
"pairwiseMalformedClientRedirectURI": "Client contained an invalid redirect URI.",
|
||||
"pairwiseClientRedirectURIsMissingHost": "Client redirect URIs must contain a valid host component.",
|
||||
"pairwiseClientRedirectURIsMultipleHosts": "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.",
|
||||
"pairwiseMalformedSectorIdentifierURI": "Malformed Sector Identifier URI.",
|
||||
"pairwiseFailedToGetRedirectURIs": "Failed to get redirect URIs from the Sector Identifier URI.",
|
||||
"pairwiseRedirectURIsMismatch": "Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI."
|
||||
},
|
||||
"es": {
|
||||
"invalidPasswordMinLengthMessage": "Contraseña incorrecta: longitud mínima {0}.",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "Contraseña incorrecta: debe contener al menos {0} letras minúsculas.",
|
||||
"invalidPasswordMinDigitsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "Contraseña incorrecta: debe contener al menos {0} letras mayúsculas.",
|
||||
"invalidPasswordMinSpecialCharsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres especiales.",
|
||||
"invalidPasswordNotUsernameMessage": "Contraseña incorrecta: no puede ser igual al nombre de usuario.",
|
||||
"invalidPasswordRegexPatternMessage": "Contraseña incorrecta: no cumple la expresión regular.",
|
||||
"invalidPasswordHistoryMessage": "Contraseña incorrecta: no puede ser igual a ninguna de las últimas {0} contraseñas."
|
||||
},
|
||||
"fr": {
|
||||
"invalidPasswordMinLengthMessage": "Mot de passe invalide : longueur minimale requise de {0}.",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.",
|
||||
"invalidPasswordMinDigitsMessage": "Mot de passe invalide : doit contenir au moins {0} chiffre(s).",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.",
|
||||
"invalidPasswordMinSpecialCharsMessage": "Mot de passe invalide : doit contenir au moins {0} caractère(s) spéciaux.",
|
||||
"invalidPasswordNotUsernameMessage": "Mot de passe invalide : ne doit pas être identique au nom d'utilisateur.",
|
||||
"invalidPasswordRegexPatternMessage": "Mot de passe invalide : ne valide pas l'expression rationnelle.",
|
||||
"invalidPasswordHistoryMessage": "Mot de passe invalide : ne doit pas être égal aux {0} derniers mot de passe."
|
||||
},
|
||||
"it": {},
|
||||
"ja": {
|
||||
"invalidPasswordMinLengthMessage": "無効なパスワード: 最小{0}の長さが必要です。",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "無効なパスワード: 少なくとも{0}文字の小文字を含む必要があります。",
|
||||
"invalidPasswordMinDigitsMessage": "無効なパスワード: 少なくとも{0}文字の数字を含む必要があります。",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "無効なパスワード: 少なくとも{0}文字の大文字を含む必要があります。",
|
||||
"invalidPasswordMinSpecialCharsMessage": "無効なパスワード: 少なくとも{0}文字の特殊文字を含む必要があります。",
|
||||
"invalidPasswordNotUsernameMessage": "無効なパスワード: ユーザー名と同じパスワードは禁止されています。",
|
||||
"invalidPasswordRegexPatternMessage": "無効なパスワード: 正規表現パターンと一致しません。",
|
||||
"invalidPasswordHistoryMessage": "無効なパスワード: 最近の{0}パスワードのいずれかと同じパスワードは禁止されています。",
|
||||
"invalidPasswordBlacklistedMessage": "無効なパスワード: パスワードがブラックリストに含まれています。",
|
||||
"invalidPasswordGenericMessage": "無効なパスワード: 新しいパスワードはパスワード・ポリシーと一致しません。",
|
||||
"ldapErrorInvalidCustomFilter": "LDAPフィルターのカスタム設定が、「(」から開始または「)」で終了となっていません。",
|
||||
"ldapErrorConnectionTimeoutNotNumber": "接続タイムアウトは数字でなければなりません",
|
||||
"ldapErrorReadTimeoutNotNumber": "読み取りタイムアウトは数字でなければなりません",
|
||||
"ldapErrorMissingClientId": "レルムロール・マッピングを使用しない場合は、クライアントIDは設定内で提供される必要があります。",
|
||||
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "グループの継承を維持することと、UIDメンバーシップ・タイプを使用することは同時にできません。",
|
||||
"ldapErrorCantWriteOnlyForReadOnlyLdap": "LDAPプロバイダー・モードがWRITABLEではない場合は、write onlyを設定することはできません。",
|
||||
"ldapErrorCantWriteOnlyAndReadOnly": "write-onlyとread-onlyを一緒に設定することはできません。",
|
||||
"ldapErrorCantEnableStartTlsAndConnectionPooling": "StartTLSと接続プーリングの両方を有効にできません。",
|
||||
"clientRedirectURIsFragmentError": "リダイレクトURIにURIフラグメントを含めることはできません。",
|
||||
"clientRootURLFragmentError": "ルートURLにURLフラグメントを含めることはできません。",
|
||||
"pairwiseMalformedClientRedirectURI": "クライアントに無効なリダイレクトURIが含まれていました。",
|
||||
"pairwiseClientRedirectURIsMissingHost": "クライアントのリダイレクトURIには有効なホスト・コンポーネントが含まれている必要があります。",
|
||||
"pairwiseClientRedirectURIsMultipleHosts": "設定されたセレクター識別子URIがない場合は、クライアントのリダイレクトURIは複数のホスト・コンポーネントを含むことはできません。",
|
||||
"pairwiseMalformedSectorIdentifierURI": "不正なセレクター識別子URIです。",
|
||||
"pairwiseFailedToGetRedirectURIs": "セクター識別子URIからリダイレクトURIを取得できませんでした。",
|
||||
"pairwiseRedirectURIsMismatch": "クライアントのリダイレクトURIは、セクター識別子URIからフェッチされたリダイレクトURIと一致しません。"
|
||||
},
|
||||
"lt": {
|
||||
"invalidPasswordMinLengthMessage": "Per trumpas slaptažodis: mažiausias ilgis {0}.",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} mažąją raidę.",
|
||||
"invalidPasswordMinDigitsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} skaitmenį.",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} didžiąją raidę.",
|
||||
"invalidPasswordMinSpecialCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} specialų simbolį.",
|
||||
"invalidPasswordNotUsernameMessage": "Neteisingas slaptažodis: slaptažodis negali sutapti su naudotojo vardu.",
|
||||
"invalidPasswordRegexPatternMessage": "Neteisingas slaptažodis: slaptažodis netenkina regex taisyklės(ių).",
|
||||
"invalidPasswordHistoryMessage": "Neteisingas slaptažodis: slaptažodis negali sutapti su prieš tai buvusiais {0} slaptažodžiais.",
|
||||
"ldapErrorInvalidCustomFilter": "Sukonfigūruotas LDAP filtras neprasideda \"(\" ir nesibaigia \")\" simboliais.",
|
||||
"ldapErrorMissingClientId": "Privaloma nurodyti kliento ID kai srities rolių susiejimas nėra nenaudojamas.",
|
||||
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Grupių paveldėjimo ir UID narystės tipas kartu negali būti naudojami.",
|
||||
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Negalima nustatyti rašymo rėžimo kuomet LDAP teikėjo rėžimas ne WRITABLE",
|
||||
"ldapErrorCantWriteOnlyAndReadOnly": "Negalima nustatyti tik rašyti ir tik skaityti kartu",
|
||||
"clientRedirectURIsFragmentError": "Nurodykite URI fragmentą, kurio negali būti peradresuojamuose URI adresuose",
|
||||
"clientRootURLFragmentError": "Nurodykite URL fragmentą, kurio negali būti šakniniame URL adrese",
|
||||
"pairwiseMalformedClientRedirectURI": "Klientas pateikė neteisingą nukreipimo nuorodą.",
|
||||
"pairwiseClientRedirectURIsMissingHost": "Kliento nukreipimo nuorodos privalo būti nurodytos su serverio vardo komponentu.",
|
||||
"pairwiseClientRedirectURIsMultipleHosts": "Kuomet nesukonfigūruotas sektoriaus identifikatoriaus URL, kliento nukreipimo nuorodos privalo talpinti ne daugiau kaip vieną skirtingą serverio vardo komponentą.",
|
||||
"pairwiseMalformedSectorIdentifierURI": "Neteisinga sektoriaus identifikatoriaus URI.",
|
||||
"pairwiseFailedToGetRedirectURIs": "Nepavyko gauti nukreipimo nuorodų iš sektoriaus identifikatoriaus URI.",
|
||||
"pairwiseRedirectURIsMismatch": "Kliento nukreipimo nuoroda neatitinka nukreipimo nuorodų iš sektoriaus identifikatoriaus URI."
|
||||
},
|
||||
"nl": {
|
||||
"invalidPasswordMinLengthMessage": "Ongeldig wachtwoord: de minimale lengte is {0} karakters.",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} kleine letters bevatten.",
|
||||
"invalidPasswordMinDigitsMessage": "Ongeldig wachtwoord: het moet minstens {0} getallen bevatten.",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} hoofdletters bevatten.",
|
||||
"invalidPasswordMinSpecialCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} speciale karakters bevatten.",
|
||||
"invalidPasswordNotUsernameMessage": "Ongeldig wachtwoord: het mag niet overeenkomen met de gebruikersnaam.",
|
||||
"invalidPasswordRegexPatternMessage": "Ongeldig wachtwoord: het voldoet niet aan het door de beheerder ingestelde patroon.",
|
||||
"invalidPasswordHistoryMessage": "Ongeldig wachtwoord: het mag niet overeen komen met een van de laatste {0} wachtwoorden.",
|
||||
"invalidPasswordGenericMessage": "Ongeldig wachtwoord: het nieuwe wachtwoord voldoet niet aan het wachtwoordbeleid.",
|
||||
"ldapErrorInvalidCustomFilter": "LDAP filter met aangepaste configuratie start niet met \"(\" of eindigt niet met \")\".",
|
||||
"ldapErrorConnectionTimeoutNotNumber": "Verbindingstimeout moet een getal zijn",
|
||||
"ldapErrorReadTimeoutNotNumber": "Lees-timeout moet een getal zijn",
|
||||
"ldapErrorMissingClientId": "Client ID moet ingesteld zijn als Realm Roles Mapping niet gebruikt wordt.",
|
||||
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Kan groepsovererving niet behouden bij UID-lidmaatschapstype.",
|
||||
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Alleen-schrijven niet mogelijk als LDAP provider mode niet WRITABLE is",
|
||||
"ldapErrorCantWriteOnlyAndReadOnly": "Alleen-schrijven en alleen-lezen mogen niet tegelijk ingesteld zijn",
|
||||
"clientRedirectURIsFragmentError": "Redirect URIs mogen geen URI fragment bevatten",
|
||||
"clientRootURLFragmentError": "Root URL mag geen URL fragment bevatten",
|
||||
"pairwiseMalformedClientRedirectURI": "Client heeft een ongeldige redirect URI.",
|
||||
"pairwiseClientRedirectURIsMissingHost": "Client redirect URIs moeten een geldige host-component bevatten.",
|
||||
"pairwiseClientRedirectURIsMultipleHosts": "Zonder een geconfigureerde Sector Identifier URI mogen client redirect URIs niet meerdere host componenten hebben.",
|
||||
"pairwiseMalformedSectorIdentifierURI": "Onjuist notatie in Sector Identifier URI.",
|
||||
"pairwiseFailedToGetRedirectURIs": "Kon geen redirect URIs verkrijgen van de Sector Identifier URI.",
|
||||
"pairwiseRedirectURIsMismatch": "Client redirect URIs komen niet overeen met redict URIs ontvangen van de Sector Identifier URI."
|
||||
},
|
||||
"no": {
|
||||
"invalidPasswordMinLengthMessage": "Ugyldig passord: minimum lengde {0}.",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "Ugyldig passord: må inneholde minst {0} små bokstaver.",
|
||||
"invalidPasswordMinDigitsMessage": "Ugyldig passord: må inneholde minst {0} sifre.",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "Ugyldig passord: må inneholde minst {0} store bokstaver.",
|
||||
"invalidPasswordMinSpecialCharsMessage": "Ugyldig passord: må inneholde minst {0} spesialtegn.",
|
||||
"invalidPasswordNotUsernameMessage": "Ugyldig passord: kan ikke være likt brukernavn.",
|
||||
"invalidPasswordRegexPatternMessage": "Ugyldig passord: tilfredsstiller ikke kravene for passord-mønster.",
|
||||
"invalidPasswordHistoryMessage": "Ugyldig passord: kan ikke være likt noen av de {0} foregående passordene.",
|
||||
"ldapErrorInvalidCustomFilter": "Tilpasset konfigurasjon av LDAP-filter starter ikke med \"(\" eller slutter ikke med \")\".",
|
||||
"ldapErrorMissingClientId": "KlientID må være tilgjengelig i config når sikkerhetsdomenerollemapping ikke brukes.",
|
||||
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Ikke mulig å bevare gruppearv og samtidig bruke UID medlemskapstype.",
|
||||
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Kan ikke sette write-only når LDAP leverandør-modus ikke er WRITABLE",
|
||||
"ldapErrorCantWriteOnlyAndReadOnly": "Kan ikke sette både write-only og read-only"
|
||||
},
|
||||
"pl": {},
|
||||
"pt-BR": {
|
||||
"invalidPasswordMinLengthMessage": "Senha inválida: deve conter ao menos {0} caracteres.",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres minúsculos.",
|
||||
"invalidPasswordMinDigitsMessage": "Senha inválida: deve conter ao menos {0} digitos numéricos.",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres maiúsculos.",
|
||||
"invalidPasswordMinSpecialCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres especiais.",
|
||||
"invalidPasswordNotUsernameMessage": "Senha inválida: não deve ser igual ao nome de usuário.",
|
||||
"invalidPasswordRegexPatternMessage": "Senha inválida: falha ao passar por padrões.",
|
||||
"invalidPasswordHistoryMessage": "Senha inválida: não deve ser igual às últimas {0} senhas.",
|
||||
"ldapErrorInvalidCustomFilter": "Filtro LDAP não inicia com \"(\" ou não termina com \")\".",
|
||||
"ldapErrorMissingClientId": "ID do cliente precisa ser definido na configuração quando mapeamentos de Roles do Realm não é utilizado.",
|
||||
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Não é possível preservar herança de grupos e usar tipo de associação de UID ao mesmo tempo.",
|
||||
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Não é possível definir modo de somente escrita quando o provedor LDAP não suporta escrita",
|
||||
"ldapErrorCantWriteOnlyAndReadOnly": "Não é possível definir somente escrita e somente leitura ao mesmo tempo",
|
||||
"clientRedirectURIsFragmentError": "URIs de redirecionamento não podem conter fragmentos",
|
||||
"clientRootURLFragmentError": "URL raiz não pode conter fragmentos"
|
||||
},
|
||||
"ru": {
|
||||
"invalidPasswordMinLengthMessage": "Некорректный пароль: длина пароля должна быть не менее {0} символов(а).",
|
||||
"invalidPasswordMinDigitsMessage": "Некорректный пароль: должен содержать не менее {0} цифр(ы).",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в нижнем регистре.",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в верхнем регистре.",
|
||||
"invalidPasswordMinSpecialCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} спецсимволов(а).",
|
||||
"invalidPasswordNotUsernameMessage": "Некорректный пароль: пароль не должен совпадать с именем пользователя.",
|
||||
"invalidPasswordRegexPatternMessage": "Некорректный пароль: пароль не прошел проверку по регулярному выражению.",
|
||||
"invalidPasswordHistoryMessage": "Некорректный пароль: пароль не должен совпадать с последним(и) {0} паролем(ями).",
|
||||
"invalidPasswordGenericMessage": "Некорректный пароль: новый пароль не соответствует правилам пароля.",
|
||||
"ldapErrorInvalidCustomFilter": "Сконфигурированный пользователем фильтр LDAP не должен начинаться с \"(\" или заканчиваться на \")\".",
|
||||
"ldapErrorMissingClientId": "Client ID должен быть настроен в конфигурации, если не используется сопоставление ролей в realm.",
|
||||
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Не удалось унаследовать группу и использовать членство UID типа вместе.",
|
||||
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Невозможно установить режим \"только на запись\", когда LDAP провайдер не в режиме WRITABLE",
|
||||
"ldapErrorCantWriteOnlyAndReadOnly": "Невозможно одновременно установить режимы \"только на чтение\" и \"только на запись\"",
|
||||
"clientRedirectURIsFragmentError": "URI перенаправления не должен содержать фрагмент URI",
|
||||
"clientRootURLFragmentError": "Корневой URL не должен содержать фрагмент URL ",
|
||||
"pairwiseMalformedClientRedirectURI": "Клиент содержит некорректный URI перенаправления.",
|
||||
"pairwiseClientRedirectURIsMissingHost": "URI перенаправления клиента должен содержать корректный компонент хоста.",
|
||||
"pairwiseClientRedirectURIsMultipleHosts": "Без конфигурации по части идентификатора URI, URI перенаправления клиента не может содержать несколько компонентов хоста.",
|
||||
"pairwiseMalformedSectorIdentifierURI": "Искаженная часть идентификатора URI.",
|
||||
"pairwiseFailedToGetRedirectURIs": "Не удалось получить идентификаторы URI перенаправления из части идентификатора URI.",
|
||||
"pairwiseRedirectURIsMismatch": "Клиент URI переадресации не соответствует URI переадресации, полученной из части идентификатора URI."
|
||||
},
|
||||
"zh-CN": {
|
||||
"invalidPasswordMinLengthMessage": "无效的密码:最短长度 {0}.",
|
||||
"invalidPasswordMinLowerCaseCharsMessage": "无效的密码:至少包含 {0} 小写字母",
|
||||
"invalidPasswordMinDigitsMessage": "无效的密码:至少包含 {0} 个数字",
|
||||
"invalidPasswordMinUpperCaseCharsMessage": "无效的密码:最短长度 {0} 大写字母",
|
||||
"invalidPasswordMinSpecialCharsMessage": "无效的密码:最短长度 {0} 特殊字符",
|
||||
"invalidPasswordNotUsernameMessage": "无效的密码: 不可以与用户名相同",
|
||||
"invalidPasswordRegexPatternMessage": "无效的密码: 无法与正则表达式匹配",
|
||||
"invalidPasswordHistoryMessage": "无效的密码:不能与最后使用的 {0} 个密码相同",
|
||||
"ldapErrorInvalidCustomFilter": "定制的 LDAP过滤器不是以 \"(\" 开头或以 \")\"结尾.",
|
||||
"ldapErrorConnectionTimeoutNotNumber": "Connection Timeout 必须是个数字",
|
||||
"ldapErrorMissingClientId": "当域角色映射未启用时,客户端 ID 需要指定。",
|
||||
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "无法在使用UID成员类型的同时维护组继承属性。",
|
||||
"ldapErrorCantWriteOnlyForReadOnlyLdap": "当LDAP提供方不是可写模式时,无法设置只写",
|
||||
"ldapErrorCantWriteOnlyAndReadOnly": "无法同时设置只读和只写",
|
||||
"clientRedirectURIsFragmentError": "重定向URL不应包含URI片段",
|
||||
"clientRootURLFragmentError": "根URL 不应包含 URL 片段",
|
||||
"pairwiseMalformedClientRedirectURI": "客户端包含一个无效的重定向URL",
|
||||
"pairwiseClientRedirectURIsMissingHost": "客户端重定向URL需要有一个有效的主机",
|
||||
"pairwiseClientRedirectURIsMultipleHosts": "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.",
|
||||
"pairwiseMalformedSectorIdentifierURI": "Malformed Sector Identifier URI.",
|
||||
"pairwiseFailedToGetRedirectURIs": "无法从服务器获得重定向URL",
|
||||
"pairwiseRedirectURIsMismatch": "客户端的重定向URI与服务器端获取的URI配置不匹配。"
|
||||
}
|
||||
} as const;
|
||||
/* spell-checker: enable */
|
638
src/lib/i18n/generated_messages/email.ts
Normal file
638
src/lib/i18n/generated_messages/email.ts
Normal file
@ -0,0 +1,638 @@
|
||||
//This code was automatically generated by running dist/bin/generate-i18n-messages.js
|
||||
//PLEASE DO NOT EDIT MANUALLY
|
||||
|
||||
/* spell-checker: disable */
|
||||
export const messages= {
|
||||
"ca": {
|
||||
"emailVerificationSubject": "Verificació d'email",
|
||||
"emailVerificationBody": "Algú ha creat un compte de {2} amb aquesta adreça de correu electrònic. Si has estat tu, fes clic a l'enllaç següent per verificar la teva adreça de correu electrònic.\n\n{0}\n\nAquest enllaç expirarà en {1} minuts.\n\nSi tu no has creat aquest compte, simplement ignora aquest missatge.",
|
||||
"emailVerificationBodyHtml": "<p>Algú ha creat un compte de {2} amb aquesta adreça de correu electrònic. Si has estat tu, fes clic a l'enllaç següent per verificar la teva adreça de correu electrònic.</p><p><a href=\"{0}\">{0}</a></p><p> Aquest enllaç expirarà en {1} minuts.</p><p> Si tu no has creat aquest compte, simplement ignora aquest missatge.</p>",
|
||||
"passwordResetSubject": "Reinicia contrasenya",
|
||||
"passwordResetBody": "Algú ha demanat de canviar les credencials del teu compte de {2}. Si has estat tu, fes clic a l'enllaç següent per a reiniciar-les.\n\n{0}\n\nAquest enllaç expirarà en {1} minuts.\n\nSi no vols reiniciar les teves credencials, simplement ignora aquest missatge i no es realitzarà cap canvi.",
|
||||
"passwordResetBodyHtml": "<p>Algú ha demanat de canviar les credencials del teu compte de {2}. Si has estat tu, fes clic a l'enllaç següent per a reiniciar-les.</p><p><a href=\"{0}\">{0}</a></p><p>Aquest enllaç expirarà en {1} minuts.</p><p>Si no vols reiniciar les teves credencials, simplement ignora aquest missatge i no es realitzarà cap canvi.</p>",
|
||||
"executeActionsSubject": "Actualitza el teu compte",
|
||||
"executeActionsBody": "L'administrador ha sol·licitat que actualitzis el teu compte de {2}. Fes clic a l'enllaç inferior per iniciar aquest procés.\n\n{0}\n\nAquest enllaç expirarà en {1} minutes.\n\nSi no estàs al tant que l'administrador hagi sol·licitat això, simplement ignora aquest missatge i no es realitzarà cap canvi.",
|
||||
"executeActionsBodyHtml": "<p>L'administrador ha sol·licitat que actualitzis el teu compte de {2}. Fes clic a l'enllaç inferior per iniciar aquest procés.</p><p><a href=\"{0}\">{0}</a></p><p>Aquest enllaç expirarà en {1} minutes.</p><p>Si no estàs al tant que l'administrador hagi sol·licitat això, simplement ignora aquest missatge i no es realitzarà cap canvi.</p>",
|
||||
"eventLoginErrorSubject": "Fallada en l'inici de sessió",
|
||||
"eventLoginErrorBody": "S'ha detectat un intent d'accés fallit al teu compte el {0} des de {1}. Si no has estat tu, si us plau contacta amb l'administrador.",
|
||||
"eventLoginErrorBodyHtml": "<p>S'ha detectat un intent d'accés fallit al teu compte el {0} des de {1}. Si no has estat tu, si us plau contacta amb l'administrador.</p>",
|
||||
"eventRemoveTotpSubject": "Esborrat OTP",
|
||||
"eventRemoveTotpBody": "OTP s'ha eliminat del teu compte el {0} des de {1}. Si no has estat tu, per favor contacta amb l'administrador.",
|
||||
"eventRemoveTotpBodyHtml": "<p>OTP s'ha eliminat del teu compte el {0} des de {1}. Si no has estat tu, si us plau contacta amb l'administrador. </ P>",
|
||||
"eventUpdatePasswordSubject": "Actualització de contrasenya",
|
||||
"eventUpdatePasswordBody": "La teva contrasenya s'ha actualitzat el {0} des de {1}. Si no has estat tu, si us plau contacta amb l'administrador.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>La teva contrasenya s'ha actualitzat el {0} des de {1}. Si no has estat tu, si us plau contacta amb l'administrador.</p>",
|
||||
"eventUpdateTotpSubject": "Actualització de OTP",
|
||||
"eventUpdateTotpBody": "OTP s'ha actualitzat al teu compte el {0} des de {1}. Si no has estat tu, si us plau contacta amb l'administrador.",
|
||||
"eventUpdateTotpBodyHtml": "<p>OTP s'ha actualitzat al teu compte el {0} des de {1}. Si no has estat tu, si us plau contacta amb l'administrador.</p>"
|
||||
},
|
||||
"cs": {
|
||||
"emailVerificationSubject": "Ověření e-mailu",
|
||||
"emailVerificationBody": "Někdo vytvořil účet {2} s touto e-mailovou adresou. Pokud jste to vy, klikněte na níže uvedený odkaz a ověřte svou e-mailovou adresu \n\n{0}\n\nTento odkaz vyprší za {1} minuty.\n\nPokud jste tento účet nevytvořili, tuto zprávu ignorujte.",
|
||||
"emailVerificationBodyHtml": "<p>Někdo vytvořil účet {2} s touto e-mailovou adresou. Pokud jste to vy, klikněte na níže uvedený odkaz a ověřte svou e-mailovou adresu. </p><p><a href=\"{0}\">Odkaz na ověření e-mailové adresy</a></p><p>Platnost odkazu vyprší za {1} minut.</p><p>Pokud jste tento účet nevytvořili, tuto zprávu ignorujte.</p>",
|
||||
"emailTestSubject": "[KEYCLOAK] - testovací zpráva",
|
||||
"emailTestBody": "Toto je testovací zpráva",
|
||||
"emailTestBodyHtml": "<p>Toto je testovací zpráva </p>",
|
||||
"identityProviderLinkSubject": "Odkaz {0}",
|
||||
"identityProviderLinkBody": "Někdo chce propojit váš účet \"{1}\" s účtem \"{0}\" uživatele {2}. Pokud jste to vy, klikněte na níže uvedený odkaz a propojte účty. \n\n{3}\n\nPlatnost tohoto odkazu je {5}.\n\nPokud nechcete propojit účet, tuto zprávu ignorujte. Pokud propojíte účty, budete se moci přihlásit jako {1} pomocí {0}.",
|
||||
"identityProviderLinkBodyHtml": "<p>Někdo právě požádal o změnu hesla u vašeho účtu {2}. Pokud jste to vy, pro jeho změnu klikněte na odkaz níže.</p><p><a href=\"{0}\">Odkaz na změnu hesla.</a></p><p>Platnost tohoto odkazu je {3}.</p><p>Pokud heslo změnit nechcete, tuto zprávu ignorujte a nic se nezmění.</p>",
|
||||
"passwordResetSubject": "Zapomenuté heslo",
|
||||
"passwordResetBody": "Někdo právě požádal o změnu hesla u vašeho účtu {2}. Pokud jste to vy, pro jeho změnu klikněte na odkaz níže.\n\n{0}\n\nPlatnost tohoto odkazu je {3}.\n\nPokud heslo změnit nechcete, tuto zprávu ignorujte a nic se nezmění.",
|
||||
"passwordResetBodyHtml": "<p> Někdo právě požádal o změnu pověření vašeho účtu {2}. Pokud jste to vy, klikněte na odkaz níže, abyste je resetovali.</p><p><a href=\"{0}\">Odkaz na obnovení pověření </a></p><p> Platnost tohoto odkazu vyprší během {1} minut.</p><p> Pokud nechcete obnovit vaše pověření, ignorujte tuto zprávu a nic se nezmění.</p>",
|
||||
"executeActionsSubject": "Aktualizujte svůj účet",
|
||||
"executeActionsBody": "Váš administrátor vás požádal o provedení následujících akcí u účtu {2}: {3}. Začněte kliknutím na níže uvedený odkaz.\n\n{0}\n\nPlatnost tohoto odkazu je {4}.\n\nPokud si nejste jisti, zda je tento požadavek v pořádku, ignorujte tuto zprávu.",
|
||||
"executeActionsBodyHtml": "<p>Váš administrátor vás požádal o provedení následujících akcí u účtu {2}: {3}. Začněte kliknutím na níže uvedený odkaz.</p><p><a href=\"{0}\">Odkaz na aktualizaci účtu.</a></p><p>Platnost tohoto odkazu je {4}.</p><p>Pokud si nejste jisti, zda je tento požadavek v pořádku, ignorujte tuto zprávu.</p>",
|
||||
"eventLoginErrorSubject": "Chyba přihlášení",
|
||||
"eventLoginErrorBody": "Někdo se neúspěšně pokusil přihlásit k účtu {0} z {1}. Pokud jste to nebyli vy, kontaktujte administrátora.",
|
||||
"eventLoginErrorBodyHtml": "<p>Někdo se neúspěšně pokusil přihlásit k účtu {0} z {1}. Pokud jste to nebyli vy, kontaktujte administrátora.</p>",
|
||||
"eventRemoveTotpSubject": "Odebrat TOTP",
|
||||
"eventRemoveTotpBody": "V účtu {0} bylo odebráno nastavení OTP z {1}. Pokud jste to nebyli vy, kontaktujte administrátora.",
|
||||
"eventRemoveTotpBodyHtml": "<p>V účtu {0} bylo odebráno nastavení OTP z {1}. Pokud jste to nebyli vy, kontaktujte administrátora.</p>",
|
||||
"eventUpdatePasswordSubject": "Aktualizace hesla",
|
||||
"eventUpdatePasswordBody": "V účtu {0} bylo změněno heslo z {1}. Pokud jste to nebyli vy, kontaktujte administrátora.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>V účtu {0} bylo změněno heslo z {1}. Pokud jste to nebyli vy, kontaktujte administrátora.</p>",
|
||||
"eventUpdateTotpSubject": "Aktualizace OTP",
|
||||
"eventUpdateTotpBody": "V účtu {0} bylo změněno nastavení OTP z {1}. Pokud jste to nebyli vy, kontaktujte administrátora.",
|
||||
"eventUpdateTotpBodyHtml": "<p>V účtu {0} bylo změněno nastavení OTP z {1}. Pokud jste to nebyli vy, kontaktujte administrátora.</p>",
|
||||
"requiredAction.CONFIGURE_TOTP": "Konfigurace OTP",
|
||||
"requiredAction.terms_and_conditions": "Smluvní podmínky",
|
||||
"requiredAction.UPDATE_PASSWORD": "Aktualizace hesla",
|
||||
"requiredAction.UPDATE_PROFILE": "Aktualizace profilu",
|
||||
"requiredAction.VERIFY_EMAIL": "Ověření e-mailu",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds": "sekund",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.1": "sekunda",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.2": "sekundy",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.3": "sekundy",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.4": "sekundy",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes": "minut",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.1": "minuta",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.2": "minuty",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.3": "minuty",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.4": "minuty",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours": "hodin",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.1": "hodina",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.2": "hodiny",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.3": "hodiny",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.4": "hodiny",
|
||||
"linkExpirationFormatter.timePeriodUnit.days": "dní",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.1": "den",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.2": "dny",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.3": "dny",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.4": "dny"
|
||||
},
|
||||
"de": {
|
||||
"emailVerificationSubject": "E-Mail verifizieren",
|
||||
"emailVerificationBody": "Jemand hat ein {2} Konto mit dieser E-Mail-Adresse erstellt. Falls Sie das waren, dann klicken Sie auf den Link, um die E-Mail-Adresse zu verifizieren.\n\n{0}\n\nDieser Link wird in {1} Minuten ablaufen.\n\nFalls Sie dieses Konto nicht erstellt haben, dann können sie diese Nachricht ignorieren.",
|
||||
"emailVerificationBodyHtml": "<p>Jemand hat ein {2} Konto mit dieser E-Mail-Adresse erstellt. Falls das Sie waren, klicken Sie auf den Link, um die E-Mail-Adresse zu verifizieren.</p><p><a href=\"{0}\">Link zur Bestätigung der E-Mail-Adresse</a></p><p>Dieser Link wird in {1} Minuten ablaufen.</p><p>Falls Sie dieses Konto nicht erstellt haben, dann können sie diese Nachricht ignorieren.</p>",
|
||||
"identityProviderLinkSubject": "Link {0}",
|
||||
"identityProviderLinkBody": "Es wurde beantragt Ihren Account {1} mit dem Account {0} von Benutzer {2} zu verlinken. Sollten Sie dies beantragt haben, klicken Sie auf den unten stehenden Link.\n\n{3}\n\n Die Gültigkeit des Links wird in {4} Minuten verfallen.\n\nSollten Sie Ihren Account nicht verlinken wollen, ignorieren Sie diese Nachricht. Wenn Sie die Accounts verlinken wird ein Login auf {1} über {0} ermöglicht.",
|
||||
"identityProviderLinkBodyHtml": "<p>Es wurde beantragt Ihren Account {1} mit dem Account {0} von Benutzer {2} zu verlinken. Sollten Sie dies beantragt haben, klicken Sie auf den unten stehenden Link.</p><p><a href=\"{3}\">Link zur Bestätigung der Kontoverknüpfung</a></p><p>Die Gültigkeit des Links wird in {4} Minuten verfallen.</p><p>Sollten Sie Ihren Account nicht verlinken wollen, ignorieren Sie diese Nachricht. Wenn Sie die Accounts verlinken wird ein Login auf {1} über {0} ermöglicht.</p>",
|
||||
"passwordResetSubject": "Passwort zurücksetzen",
|
||||
"passwordResetBody": "Es wurde eine Änderung der Anmeldeinformationen für Ihren Account {2} angefordert. Wenn Sie diese Änderung beantragt haben, klicken Sie auf den unten stehenden Link.\n\n{0}\n\nDie Gültigkeit des Links wird in {1} Minuten verfallen.\n\nSollten Sie keine Änderung vollziehen wollen können Sie diese Nachricht ignorieren und an Ihrem Account wird nichts geändert.",
|
||||
"passwordResetBodyHtml": "<p>Es wurde eine Änderung der Anmeldeinformationen für Ihren Account {2} angefordert. Wenn Sie diese Änderung beantragt haben, klicken Sie auf den unten stehenden Link.</p><p><a href=\"{0}\">Link zum Zurücksetzen von Anmeldeinformationen</a></p><p>Die Gültigkeit des Links wird in {1} Minuten verfallen.</p><p>Sollten Sie keine Änderung vollziehen wollen können Sie diese Nachricht ignorieren und an Ihrem Account wird nichts geändert.</p>",
|
||||
"executeActionsSubject": "Aktualisieren Sie Ihr Konto",
|
||||
"executeActionsBody": "Ihr Administrator hat Sie aufgefordert Ihren Account {2} zu aktualisieren. Klicken Sie auf den unten stehenden Link um den Prozess zu starten.\n\n{0}\n\nDie Gültigkeit des Links wird in {1} Minuten verfallen.\n\nSollten Sie sich dieser Aufforderung nicht bewusst sein, ignorieren Sie diese Nachricht und Ihr Account bleibt unverändert.",
|
||||
"executeActionsBodyHtml": "<p>Ihr Administrator hat Sie aufgefordert Ihren Account {2} zu aktualisieren. Klicken Sie auf den unten stehenden Link um den Prozess zu starten.</p><p><a href=\"{0}\">Link zum Account-Update</a></p><p>Die Gültigkeit des Links wird in {1} Minuten verfallen.</p><p>Sollten Sie sich dieser Aufforderung nicht bewusst sein, ignorieren Sie diese Nachricht und Ihr Account bleibt unverändert.</p>",
|
||||
"eventLoginErrorSubject": "Fehlgeschlagene Anmeldung",
|
||||
"eventLoginErrorBody": "Jemand hat um {0} von {1} versucht, sich mit Ihrem Konto anzumelden. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.",
|
||||
"eventLoginErrorBodyHtml": "<p>Jemand hat um {0} von {1} versucht, sich mit Ihrem Konto anzumelden. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.</p>",
|
||||
"eventRemoveTotpSubject": "OTP Entfernt",
|
||||
"eventRemoveTotpBody": "OTP wurde von Ihrem Konto am {0} von {1} entfernt. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.",
|
||||
"eventRemoveTotpBodyHtml": "<p>OTP wurde von Ihrem Konto am {0} von {1} entfernt. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.</p>",
|
||||
"eventUpdatePasswordSubject": "Passwort Aktualisiert",
|
||||
"eventUpdatePasswordBody": "Ihr Passwort wurde am {0} von {1} geändert. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Ihr Passwort wurde am {0} von {1} geändert. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.</p>",
|
||||
"eventUpdateTotpSubject": "OTP Aktualisiert",
|
||||
"eventUpdateTotpBody": "OTP wurde am {0} von {1} geändert. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.",
|
||||
"eventUpdateTotpBodyHtml": "<p>OTP wurde am {0} von {1} geändert. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.</p>"
|
||||
},
|
||||
"en": {
|
||||
"emailVerificationSubject": "Verify email",
|
||||
"emailVerificationBody": "Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address\n\n{0}\n\nThis link will expire within {3}.\n\nIf you didn't create this account, just ignore this message.",
|
||||
"emailVerificationBodyHtml": "<p>Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address</p><p><a href=\"{0}\">Link to e-mail address verification</a></p><p>This link will expire within {3}.</p><p>If you didn't create this account, just ignore this message.</p>",
|
||||
"emailTestSubject": "[KEYCLOAK] - SMTP test message",
|
||||
"emailTestBody": "This is a test message",
|
||||
"emailTestBodyHtml": "<p>This is a test message</p>",
|
||||
"identityProviderLinkSubject": "Link {0}",
|
||||
"identityProviderLinkBody": "Someone wants to link your \"{1}\" account with \"{0}\" account of user {2} . If this was you, click the link below to link accounts\n\n{3}\n\nThis link will expire within {5}.\n\nIf you don't want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}.",
|
||||
"identityProviderLinkBodyHtml": "<p>Someone wants to link your <b>{1}</b> account with <b>{0}</b> account of user {2} . If this was you, click the link below to link accounts</p><p><a href=\"{3}\">Link to confirm account linking</a></p><p>This link will expire within {5}.</p><p>If you don't want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}.</p>",
|
||||
"passwordResetSubject": "Reset password",
|
||||
"passwordResetBody": "Someone just requested to change your {2} account's credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {3}.\n\nIf you don't want to reset your credentials, just ignore this message and nothing will be changed.",
|
||||
"passwordResetBodyHtml": "<p>Someone just requested to change your {2} account's credentials. If this was you, click on the link below to reset them.</p><p><a href=\"{0}\">Link to reset credentials</a></p><p>This link will expire within {3}.</p><p>If you don't want to reset your credentials, just ignore this message and nothing will be changed.</p>",
|
||||
"executeActionsSubject": "Update Your Account",
|
||||
"executeActionsBody": "Your administrator has just requested that you update your {2} account by performing the following action(s): {3}. Click on the link below to start this process.\n\n{0}\n\nThis link will expire within {4}.\n\nIf you are unaware that your administrator has requested this, just ignore this message and nothing will be changed.",
|
||||
"executeActionsBodyHtml": "<p>Your administrator has just requested that you update your {2} account by performing the following action(s): {3}. Click on the link below to start this process.</p><p><a href=\"{0}\">Link to account update</a></p><p>This link will expire within {4}.</p><p>If you are unaware that your administrator has requested this, just ignore this message and nothing will be changed.</p>",
|
||||
"eventLoginErrorSubject": "Login error",
|
||||
"eventLoginErrorBody": "A failed login attempt was detected to your account on {0} from {1}. If this was not you, please contact an administrator.",
|
||||
"eventLoginErrorBodyHtml": "<p>A failed login attempt was detected to your account on {0} from {1}. If this was not you, please contact an administrator.</p>",
|
||||
"eventRemoveTotpSubject": "Remove OTP",
|
||||
"eventRemoveTotpBody": "OTP was removed from your account on {0} from {1}. If this was not you, please contact an administrator.",
|
||||
"eventRemoveTotpBodyHtml": "<p>OTP was removed from your account on {0} from {1}. If this was not you, please contact an administrator.</p>",
|
||||
"eventUpdatePasswordSubject": "Update password",
|
||||
"eventUpdatePasswordBody": "Your password was changed on {0} from {1}. If this was not you, please contact an administrator.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Your password was changed on {0} from {1}. If this was not you, please contact an administrator.</p>",
|
||||
"eventUpdateTotpSubject": "Update OTP",
|
||||
"eventUpdateTotpBody": "OTP was updated for your account on {0} from {1}. If this was not you, please contact an administrator.",
|
||||
"eventUpdateTotpBodyHtml": "<p>OTP was updated for your account on {0} from {1}. If this was not you, please contact an administrator.</p>",
|
||||
"requiredAction.CONFIGURE_TOTP": "Configure OTP",
|
||||
"requiredAction.terms_and_conditions": "Terms and Conditions",
|
||||
"requiredAction.UPDATE_PASSWORD": "Update Password",
|
||||
"requiredAction.UPDATE_PROFILE": "Update Profile",
|
||||
"requiredAction.VERIFY_EMAIL": "Verify Email",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds": "seconds",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.1": "second",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes": "minutes",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.1": "minute",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours": "hours",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.1": "hour",
|
||||
"linkExpirationFormatter.timePeriodUnit.days": "days",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.1": "day",
|
||||
"emailVerificationBodyCode": "Please verify your email address by entering in the following code.\n\n{0}\n\n.",
|
||||
"emailVerificationBodyCodeHtml": "<p>Please verify your email address by entering in the following code.</p><p><b>{0}</b></p>"
|
||||
},
|
||||
"es": {
|
||||
"emailVerificationSubject": "Verificación de email",
|
||||
"emailVerificationBody": "Alguien ha creado una cuenta de {2} con esta dirección de email. Si has sido tú, haz click en el enlace siguiente para verificar tu dirección de email.\n\n{0}\n\nEste enlace expirará en {1} minutos.\n\nSi tú no has creado esta cuenta, simplemente ignora este mensaje.",
|
||||
"emailVerificationBodyHtml": "<p>Alguien ha creado una cuenta de {2} con esta dirección de email. Si has sido tú, haz click en el enlace siguiente para verificar tu dirección de email.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirará en {1} minutos.</p><p>Si tú no has creado esta cuenta, simplemente ignora este mensaje.</p>",
|
||||
"passwordResetSubject": "Reiniciar contraseña",
|
||||
"passwordResetBody": "Alguien ha solicitado cambiar las credenciales de tu cuenta de {2}. Si has sido tú, haz clic en el enlace siguiente para reiniciarlas.\n\n{0}\n\nEste enlace expirará en {1} minutos.\n\nSi no quieres reiniciar tus credenciales, simplemente ignora este mensaje y no se realizará ningún cambio.",
|
||||
"passwordResetBodyHtml": "<p>Alguien ha solicitado cambiar las credenciales de tu cuenta de {2}. Si has sido tú, haz clic en el enlace siguiente para reiniciarlas.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirará en {1} minutos.</p><p>Si no quieres reiniciar tus credenciales, simplemente ignora este mensaje y no se realizará ningún cambio.</p>",
|
||||
"executeActionsSubject": "Actualiza tu cuenta",
|
||||
"executeActionsBody": "El administrador ha solicitado que actualices tu cuenta de {2}. Haz clic en el enlace inferior para iniciar este proceso.\n\n{0}\n\nEste enlace expirará en {1} minutos.\n\nSi no estás al tanto de que el administrador haya solicitado esto, simplemente ignora este mensaje y no se realizará ningún cambio.",
|
||||
"executeActionsBodyHtml": "<p>El administrador ha solicitado que actualices tu cuenta de {2}. Haz clic en el enlace inferior para iniciar este proceso.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirará en {1} minutos.</p><p>Si no estás al tanto de que el administrador haya solicitado esto, simplemente ignora este mensaje y no se realizará ningún cambio.</p>",
|
||||
"eventLoginErrorSubject": "Fallo en el inicio de sesión",
|
||||
"eventLoginErrorBody": "Se ha detectado un intento de acceso fallido a tu cuenta el {0} desde {1}. Si no has sido tú, por favor contacta con el administrador.",
|
||||
"eventLoginErrorBodyHtml": "<p>Se ha detectado un intento de acceso fallido a tu cuenta el {0} desde {1}. Si no has sido tú, por favor contacta con el administrador.</p>",
|
||||
"eventRemoveTotpSubject": "Borrado OTP",
|
||||
"eventRemoveTotpBody": "OTP fue eliminado de tu cuenta el {0} desde {1}. Si no has sido tú, por favor contacta con el administrador.",
|
||||
"eventRemoveTotpBodyHtml": "<p>OTP fue eliminado de tu cuenta el {0} desde {1}. Si no has sido tú, por favor contacta con el administrador.</p>",
|
||||
"eventUpdatePasswordSubject": "Actualización de contraseña",
|
||||
"eventUpdatePasswordBody": "Tu contraseña se ha actualizado el {0} desde {1}. Si no has sido tú, por favor contacta con el administrador.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Tu contraseña se ha actualizado el {0} desde {1}. Si no has sido tú, por favor contacta con el administrador.</p>",
|
||||
"eventUpdateTotpSubject": "Actualización de OTP",
|
||||
"eventUpdateTotpBody": "OTP se ha actualizado en tu cuenta el {0} desde {1}. Si no has sido tú, por favor contacta con el administrador.",
|
||||
"eventUpdateTotpBodyHtml": "<p>OTP se ha actualizado en tu cuenta el {0} desde {1}. Si no has sido tú, por favor contacta con el administrador.</p>"
|
||||
},
|
||||
"fr": {
|
||||
"emailVerificationSubject": "Vérification du courriel",
|
||||
"emailVerificationBody": "Quelqu'un vient de créer un compte \"{2}\" avec votre courriel. Si vous êtes à l'origine de cette requête, veuillez cliquer sur le lien ci-dessous afin de vérifier votre adresse de courriel\n\n{0}\n\nCe lien expire dans {1} minute(s).\n\nSinon, veuillez ignorer ce message.",
|
||||
"emailVerificationBodyHtml": "<p>Quelqu'un vient de créer un compte \"{2}\" avec votre courriel. Si vous êtes à l'origine de cette requête, veuillez cliquer sur le lien ci-dessous afin de vérifier votre adresse de courriel</p><p><a href=\"{0}\">{0}</a></p><p>Ce lien expire dans {1} minute(s).</p><p>Sinon, veuillez ignorer ce message.</p>",
|
||||
"passwordResetSubject": "Réinitialiser le mot de passe",
|
||||
"passwordResetBody": "Quelqu'un vient de demander une réinitialisation de mot de passe pour votre compte {2}. Si vous êtes à l'origine de cette requête, veuillez cliquer sur le lien ci-dessous pour le mettre à jour.\n\n{0}\n\nCe lien expire dans {1} minute(s).\n\nSinon, veuillez ignorer ce message ; aucun changement ne sera effectué sur votre compte.",
|
||||
"passwordResetBodyHtml": "<p>Quelqu'un vient de demander une réinitialisation de mot de passe pour votre compte {2}. Si vous êtes à l'origine de cette requête, veuillez cliquer sur le lien ci-dessous pour le mettre à jour.</p><p><a href=\"{0}\">Lien pour réinitialiser votre mot de passe</a></p><p>Ce lien expire dans {1} minute(s).</p><p>Sinon, veuillez ignorer ce message ; aucun changement ne sera effectué sur votre compte.</p>",
|
||||
"executeActionsSubject": "Mettre à jour votre compte",
|
||||
"executeActionsBody": "Votre administrateur vient de demander une mise à jour de votre compte {2}. Veuillez cliquer sur le lien ci-dessous afin de commencer le processus.\n\n{0}\n\nCe lien expire dans {1} minute(s).\n\nSi vous n'êtes pas à l'origine de cette requête, veuillez ignorer ce message ; aucun changement ne sera effectué sur votre compte.",
|
||||
"executeActionsBodyHtml": "<p>Votre administrateur vient de demander une mise à jour de votre compte {2}. Veuillez cliquer sur le lien ci-dessous afin de commencer le processus.</p><p><a href=\"{0}\">{0}</a></p><p>Ce lien expire dans {1} minute(s).</p><p>Si vous n'êtes pas à l'origine de cette requête, veuillez ignorer ce message ; aucun changement ne sera effectué sur votre compte.</p>",
|
||||
"eventLoginErrorSubject": "Erreur de connexion",
|
||||
"eventLoginErrorBody": "Une tentative de connexion a été détectée sur votre compte {0} depuis {1}. Si vous n'êtes pas à l'origine de cette requête, veuillez contacter votre administrateur.",
|
||||
"eventLoginErrorBodyHtml": "<p>Une tentative de connexion a été détectée sur votre compte {0} depuis {1}. Si vous n'êtes pas à l'origine de cette requête, veuillez contacter votre administrateur.</p>",
|
||||
"eventRemoveTotpSubject": "Suppression du OTP",
|
||||
"eventRemoveTotpBody": "Le OTP a été supprimé de votre compte {0} depuis {1}. Si vous n'étiez pas à l'origine de cette requête, veuillez contacter votre administrateur.",
|
||||
"eventRemoveTotpBodyHtml": "<p>Le OTP a été supprimé de votre compte {0} depuis {1}. Si vous n'étiez pas à l'origine de cette requête, veuillez contacter votre administrateur.</p>",
|
||||
"eventUpdatePasswordSubject": "Mise à jour du mot de passe",
|
||||
"eventUpdatePasswordBody": "Votre mot de passe pour votre compte {0} a été modifié depuis {1}. Si vous n'étiez pas à l'origine de cette requête, veuillez contacter votre administrateur.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Votre mot de passe pour votre compte {0} a été modifié depuis {1}. Si vous n'étiez pas à l'origine de cette requête, veuillez contacter votre administrateur.</p>",
|
||||
"eventUpdateTotpSubject": "Mise à jour du OTP",
|
||||
"eventUpdateTotpBody": "Le OTP a été mis à jour pour votre compte {0} depuis {1}. Si vous n'étiez pas à l'origine de cette requête, veuillez contacter votre administrateur.",
|
||||
"eventUpdateTotpBodyHtml": "<p>Le OTP a été mis à jour pour votre compte {0} depuis {1}. Si vous n'étiez pas à l'origine de cette requête, veuillez contacter votre administrateur.</p>"
|
||||
},
|
||||
"it": {
|
||||
"emailVerificationSubject": "Verifica l'email",
|
||||
"emailVerificationBody": "Qualcuno ha creato un account {2} con questo indirizzo email. Se sei stato tu, fai clic sul link seguente per verificare il tuo indirizzo email\n\n{0}\n\nQuesto link scadrà in {3}.\n\nSe non sei stato tu a creare questo account, ignora questo messaggio.",
|
||||
"emailVerificationBodyHtml": "<p>Qualcuno ha creato un account {2} con questo indirizzo email. Se sei stato tu, fai clic sul link seguente per verificare il tuo indirizzo email</p><p><a href=\"{0}\">{0}</a></p><p>Questo link scadrà in {3}.</p><p>Se non sei stato tu a creare questo account, ignora questo messaggio.</p>",
|
||||
"emailTestSubject": "[KEYCLOAK] - messaggio di test SMTP",
|
||||
"emailTestBody": "Questo è un messaggio di test",
|
||||
"emailTestBodyHtml": "<p>Questo è un messaggio di test</p>",
|
||||
"identityProviderLinkSubject": "Link {0}",
|
||||
"identityProviderLinkBody": "Qualcuno vuole associare il tuo account \"{1}\" con l'account \"{0}\" dell'utente {2}. Se sei stato tu, fai clic sul link seguente per associare gli account\n\n{3}\n\nQuesto link scadrà in {5}.\n\nSe non vuoi associare l'account, ignora questo messaggio. Se associ gli account, potrai accedere a {1} attraverso {0}.",
|
||||
"identityProviderLinkBodyHtml": "<p>Qualcuno vuole associare il tuo account <b>{1}</b> con l'account <b>{0}</b> dell'utente {2}. Se sei stato tu, fai clic sul link seguente per associare gli account</p><p><a href=\"{3}\">{3}</a></p><p>Questo link scadrà in {5}.</p><p>Se non vuoi associare l'account, ignora questo messaggio. Se associ gli account, potrai accedere a {1} attraverso {0}.</p>",
|
||||
"passwordResetSubject": "Reimposta la password",
|
||||
"passwordResetBody": "Qualcuno ha appena richiesto di cambiare le credenziali di accesso al tuo account {2}. Se sei stato tu, fai clic sul link seguente per reimpostarle.\n\n{0}\n\nQuesto link e codice scadranno in {3}.\n\nSe non vuoi reimpostare le tue credenziali di accesso, ignora questo messaggio e non verrà effettuato nessun cambio.",
|
||||
"passwordResetBodyHtml": "<p>Qualcuno ha appena richiesto di cambiare le credenziali di accesso al tuo account {2}. Se sei stato tu, fai clic sul link seguente per reimpostarle.</p><p><a href=\"{0}\">{0}</a></p><p>Questo link scadrà in {3}.</p><p>Se non vuoi reimpostare le tue credenziali di accesso, ignora questo messaggio e non verrà effettuato nessun cambio.</p>",
|
||||
"executeActionsSubject": "Aggiorna il tuo account",
|
||||
"executeActionsBody": "Il tuo amministratore ha appena richiesto un aggiornamento del tuo account {2} ed è necessario che tu esegua la/le seguente/i azione/i: {3}. Fai clic sul link seguente per iniziare questo processo.\n\n{0}\n\nQuesto link scadrà in {4}.\n\nSe non sei a conoscenza della richiesta del tuo amministratore, ignora questo messaggio e non verrà effettuato nessun cambio.",
|
||||
"executeActionsBodyHtml": "<p>Il tuo amministratore ha appena richiesto un aggiornamento del tuo account {2} ed è necessario che tu esegua la/le seguente/i azione/i: {3}. Fai clic sul link seguente per iniziare questo processo.</p><p><a href=\"{0}\">Link to account update</a></p><p>Questo link scadrà in {4}.</p><p>Se non sei a conoscenza della richiesta del tuo amministratore, ignora questo messaggio e non verrà effettuato nessun cambio.</p>",
|
||||
"eventLoginErrorSubject": "Errore di accesso",
|
||||
"eventLoginErrorBody": "È stato rilevato un tentativo fallito di accesso al tuo account il {0} da {1}. Se non sei stato tu, per favore contatta l'amministratore.",
|
||||
"eventLoginErrorBodyHtml": "<p>È stato rilevato un tentativo fallito di accesso al tuo account il {0} da {1}. Se non sei stato tu, per favore contatta l'amministratore.</p>",
|
||||
"eventRemoveTotpSubject": "Rimozione OTP (password temporanea valida una volta sola)",
|
||||
"eventRemoveTotpBody": "La OTP (password temporanea valida una volta sola) è stata rimossa dal tuo account il {0} da {1}. Se non sei stato tu, per favore contatta l'amministratore.",
|
||||
"eventRemoveTotpBodyHtml": "<p>La OTP (password temporanea valida una volta sola) è stata rimossa dal tuo account il {0} da {1}. Se non sei stato tu, per favore contatta l'amministratore.</p>",
|
||||
"eventUpdatePasswordSubject": "Aggiornamento password",
|
||||
"eventUpdatePasswordBody": "La tua password è stata cambiata il {0} da {1}. Se non sei stato tu, per favore contatta l'amministratore.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>La tua password è stata cambiata il {0} da {1}. Se non sei stato tu, per favore contatta l'amministratore.</p>",
|
||||
"eventUpdateTotpSubject": "Aggiornamento OTP (password temporanea valida una volta sola)",
|
||||
"eventUpdateTotpBody": "La OTP (password temporanea valida una volta sola) è stata aggiornata per il tuo account il {0} da {1}. Se non sei stato tu, per favore contatta l'amministratore.",
|
||||
"eventUpdateTotpBodyHtml": "<p>La OTP (password temporanea valida una volta sola) è stata aggiornata per il tuo account il {0} da {1}. Se non sei stato tu, per favore contatta l'amministratore.</p>",
|
||||
"requiredAction.CONFIGURE_TOTP": "Configurazione OTP",
|
||||
"requiredAction.terms_and_conditions": "Termini e condizioni",
|
||||
"requiredAction.UPDATE_PASSWORD": "Aggiornamento password",
|
||||
"requiredAction.UPDATE_PROFILE": "Aggiornamento profilo",
|
||||
"requiredAction.VERIFY_EMAIL": "Verifica dell'indirizzo email",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds": "secondi",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.1": "secondo",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes": "minuti",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.1": "minuto",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours": "ore",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.1": "ora",
|
||||
"linkExpirationFormatter.timePeriodUnit.days": "giorni",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.1": "giorno",
|
||||
"emailVerificationBodyCode": "Per favore verifica il tuo indirizzo email inserendo il codice seguente.\n\n{0}\n\n.",
|
||||
"emailVerificationBodyCodeHtml": "<p>Per favore verifica il tuo indirizzo email inserendo il codice seguente.</p><p><b>{0}</b></p>"
|
||||
},
|
||||
"ja": {
|
||||
"emailVerificationSubject": "Eメールの確認",
|
||||
"emailVerificationBody": "このメールアドレスで{2}アカウントが作成されました。以下のリンクをクリックしてメールアドレスの確認を完了してください。\n\n{0}\n\nこのリンクは{3}だけ有効です。\n\nもしこのアカウントの作成に心当たりがない場合は、このメールを無視してください。",
|
||||
"emailVerificationBodyHtml": "<p>このメールアドレスで{2}アカウントが作成されました。以下のリンクをクリックしてメールアドレスの確認を完了してください。</p><p><a href=\"{0}\">メールアドレスの確認</a></p><p>このリンクは{3}だけ有効です。</p><p>もしこのアカウントの作成に心当たりがない場合は、このメールを無視してください。</p>",
|
||||
"emailTestSubject": "[KEYCLOAK] - SMTPテストメッセージ",
|
||||
"emailTestBody": "これはテストメッセージです",
|
||||
"emailTestBodyHtml": "<p>これはテストメッセージです</p>",
|
||||
"identityProviderLinkSubject": "リンク {0}",
|
||||
"identityProviderLinkBody": "あなたの\"{1}\"アカウントと{2}ユーザーの\"{0}\"アカウントのリンクが要求されました。以下のリンクをクリックしてアカウントのリンクを行ってください。\n\n{3}\n\nこのリンクは{5}だけ有効です。\n\nもしアカウントのリンクを行わない場合は、このメッセージを無視してください。アカウントのリンクを行うことで、{0}経由で{1}にログインすることができるようになります。",
|
||||
"identityProviderLinkBodyHtml": "<p>あなたの<b>{1}</b>アカウントと{2}ユーザーの<b>{0}</b>アカウントのリンクが要求されました。以下のリンクをクリックしてアカウントのリンクを行ってください。</p><p><a href=\"{3}\">アカウントリンクの確認</a></p><p>このリンクは{5}だけ有効です。</p><p>もしアカウントのリンクを行わない場合は、このメッセージを無視してください。アカウントのリンクを行うことで、{0}経由で{1}にログインすることができるようになります。</p>",
|
||||
"passwordResetSubject": "パスワードのリセット",
|
||||
"passwordResetBody": "あなたの{2}アカウントのパスワードの変更が要求されています。以下のリンクをクリックしてパスワードのリセットを行ってください。\n\n{0}\n\nこのリンクは{3}だけ有効です。\n\nもしパスワードのリセットを行わない場合は、このメッセージを無視してください。何も変更されません。",
|
||||
"passwordResetBodyHtml": "<p>あなたの{2}アカウントのパスワードの変更が要求されています。以下のリンクをクリックしてパスワードのリセットを行ってください。</p><p><a href=\"{0}\">パスワードのリセット</a></p><p>このリンクは{3}だけ有効です。</p><p>もしパスワードのリセットを行わない場合は、このメッセージを無視してください。何も変更されません。</p>",
|
||||
"executeActionsSubject": "アカウントの更新",
|
||||
"executeActionsBody": "次のアクションを実行することにより、管理者よりあなたの{2}アカウントの更新が要求されています: {3}。以下のリンクをクリックしてこのプロセスを開始してください。\n\n{0}\n\nこのリンクは{4}だけ有効です。\n\n管理者からのこの変更要求についてご存知ない場合は、このメッセージを無視してください。何も変更されません。",
|
||||
"executeActionsBodyHtml": "<p>次のアクションを実行することにより、管理者よりあなたの{2}アカウントの更新が要求されています: {3}。以下のリンクをクリックしてこのプロセスを開始してください。</p><p><a href=\"{0}\">アカウントの更新</a></p><p>このリンクは{4}だけ有効です。</p><p>管理者からのこの変更要求についてご存知ない場合は、このメッセージを無視してください。何も変更されません。</p>",
|
||||
"eventLoginErrorSubject": "ログインエラー",
|
||||
"eventLoginErrorBody": "{0}に{1}からのログイン失敗があなたのアカウントで検出されました。心当たりがない場合は、管理者に連絡してください。",
|
||||
"eventLoginErrorBodyHtml": "<p>{0}に{1}からのログイン失敗があなたのアカウントで検出されました。心当たりがない場合は管理者に連絡してください。</p>",
|
||||
"eventRemoveTotpSubject": "OTPの削除",
|
||||
"eventRemoveTotpBody": "{0}に{1}からの操作でOTPが削除されました。心当たりがない場合は、管理者に連絡してください。",
|
||||
"eventRemoveTotpBodyHtml": "<p>{0}に{1}からの操作でOTPが削除されました。心当たりがない場合は、管理者に連絡してください。</p>",
|
||||
"eventUpdatePasswordSubject": "パスワードの更新",
|
||||
"eventUpdatePasswordBody": "{0}に{1}からの操作であなたのパスワードが変更されました。心当たりがない場合は、管理者に連絡してください。",
|
||||
"eventUpdatePasswordBodyHtml": "<p>{0}に{1}からの操作であなたのパスワードが変更されました。心当たりがない場合は、管理者に連絡してください。</p>",
|
||||
"eventUpdateTotpSubject": "OTPの更新",
|
||||
"eventUpdateTotpBody": "{0}に{1}からの操作でOTPが更新されました。心当たりがない場合は、管理者に連絡してください。",
|
||||
"eventUpdateTotpBodyHtml": "<p>{0}に{1}からの操作でOTPが更新されました。心当たりがない場合は、管理者に連絡してください。</p>",
|
||||
"requiredAction.CONFIGURE_TOTP": "OTPの設定",
|
||||
"requiredAction.terms_and_conditions": "利用規約",
|
||||
"requiredAction.UPDATE_PASSWORD": "パスワードの更新",
|
||||
"requiredAction.UPDATE_PROFILE": "プロファイルの更新",
|
||||
"requiredAction.VERIFY_EMAIL": "Eメールの確認",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds": "秒",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.1": "秒",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes": "分",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.1": "分",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours": "時間",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.1": "時間",
|
||||
"linkExpirationFormatter.timePeriodUnit.days": "日",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.1": "日",
|
||||
"emailVerificationBodyCode": "次のコードを入力してメールアドレスを確認してください。\n\n{0}\n\n.",
|
||||
"emailVerificationBodyCodeHtml": "<p>次のコードを入力してメールアドレスを確認してください。</p><p><b>{0}</b></p>"
|
||||
},
|
||||
"lt": {
|
||||
"emailVerificationSubject": "El. pašto patvirtinimas",
|
||||
"emailVerificationBody": "Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą\n\n{0}\n\nŠi nuoroda galioja {1} min.\n\nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką. ",
|
||||
"emailVerificationBodyHtml": "<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tao buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href=LT\"{0}\">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>",
|
||||
"identityProviderLinkSubject": "Sąsaja {0}",
|
||||
"identityProviderLinkBody": "Kažas pageidauja susieti Jūsų \"{1}\" paskyrą su \"{0}\" {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras\n\n{3}\n\nŠi nuoroda galioja {4} min.\n\nJei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.",
|
||||
"identityProviderLinkBodyHtml": "<p>žas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href=LT\"{3}\">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>",
|
||||
"passwordResetSubject": "Slaptažodžio atkūrimas",
|
||||
"passwordResetBody": "Kažkas pageidauja pakeisti Jūsų paskyros {2} slaptažodį. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą slaptažodžio pakeitimui.\n\n{0}\n\nŠi nuoroda ir kodas galioja {1} min.\n\nJei nepageidajate keisti slaptažodžio, tuomet ignoruokite šį laišką ir niekas nebus pakeista.",
|
||||
"passwordResetBodyHtml": "<p>Kažkas pageidauja pakeisti Jūsų paskyros {2} slaptažodį. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą slaptažodžio pakeitimui.</p><p><a href=LT\"{0}\">{0}</a></p><p>Ši nuoroda ir kodas galioja {1} min.</p><p>Jei nepageidajate keisti slaptažodžio, tuomet ignoruokite šį laišką ir niekas nebus pakeista.</p>",
|
||||
"executeActionsSubject": "Atnaujinkite savo paskyrą",
|
||||
"executeActionsBody": "Sistemos administratorius pageidauja, kad Jūs atnaujintumėte savo {2} paskyrą. Paspauskite žemiau esančią nuorodą paskyros duomenų atnaujinimui.\n\n{0}\n\nŠi nuoroda galioja {1} min.\n\nJei Jūs neasate tikri, kad tai administratoriaus pageidavimas, tuomet ignoruokite šį laišką ir niekas nebus pakeista.",
|
||||
"executeActionsBodyHtml": "<p>Sistemos administratorius pageidauja, kad Jūs atnaujintumėte savo {2} paskyrą. Paspauskite žemiau esančią nuorodą paskyros duomenų atnaujinimui.</p><p><a href=LT\"{0}\">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>Jei Jūs neasate tikri, kad tai administratoriaus pageidavimas, tuomet ignoruokite šį laišką ir niekas nebus pakeista.</p>",
|
||||
"eventLoginErrorSubject": "Nesėkmingas bandymas prisijungti prie jūsų paskyros",
|
||||
"eventLoginErrorBody": "Bandymas prisijungti prie jūsų paskyros {0} iš {1} nesėkmingas. Jei tai nebuvote jūs, tuomet susisiekite su administratoriumi",
|
||||
"eventLoginErrorBodyHtml": "<p>Bandymas prisijungti prie jūsų paskyros {0} iš {1} nesėkmingas. Jei tai nebuvote jūs, tuomet susisiekite su administratoriumi</p>",
|
||||
"eventRemoveTotpSubject": "OTP pašalinimas",
|
||||
"eventRemoveTotpBody": "Kažkas pageidauja atsieti TOPT Jūsų {1} paskyroje su {0}. Jei tai nebuvote Jūs, tuomet susisiekite su administratoriumi",
|
||||
"eventRemoveTotpBodyHtml": "<p>Kažkas pageidauja atsieti TOPT Jūsų <b>{1}</b> paskyroje su <b>{0}</b>. Jei tai nebuvote Jūs, tuomet susisiekite su administratoriumi</p>",
|
||||
"eventUpdatePasswordSubject": "Slaptažodžio atnaujinimas",
|
||||
"eventUpdatePasswordBody": "{1} paskyroje {0} pakeisas jūsų slaptažodis. Jei Jūs nekeitėte, tuomet susisiekite su administratoriumi",
|
||||
"eventUpdatePasswordBodyHtml": "<p>{1} paskyroje {0} pakeisas jūsų slaptažodis. Jei Jūs nekeitėte, tuomet susisiekite su administratoriumi</p>",
|
||||
"eventUpdateTotpSubject": "OTP atnaujinimas",
|
||||
"eventUpdateTotpBody": "OTP Jūsų {1} paskyroje su {0} buvo atnaujintas. Jei tai nebuvote Jūs, tuomet susisiekite su administratoriumi",
|
||||
"eventUpdateTotpBodyHtml": "<p>OTP Jūsų {1} paskyroje su {0} buvo atnaujintas. Jei tai nebuvote Jūs, tuomet susisiekite su administratoriumi</p>"
|
||||
},
|
||||
"nl": {
|
||||
"emailVerificationSubject": "Bevestig e-mailadres",
|
||||
"emailVerificationBody": "Iemand heeft een {2} account aangemaakt met dit e-mailadres. Als u dit was, klikt u op de onderstaande koppeling om uw e-mailadres te bevestigen \n\n{0}\n\nDeze koppeling zal binnen {3} vervallen.\n\nU kunt dit bericht negeren indien u dit account niet heeft aangemaakt.",
|
||||
"emailVerificationBodyHtml": "<p>Iemand heeft een {2} account aangemaakt met dit e-mailadres. Als u dit was, klikt u op de onderstaande koppeling om uw e-mailadres te bevestigen</p><p><a href=\"{0}\">Koppeling naar e-mailadres bevestiging</a></p><p>Deze koppeling zal binnen {3} vervallen.</p<p>U kunt dit bericht negeren indien u dit account niet heeft aangemaakt.</p>",
|
||||
"emailTestSubject": "[KEYCLOAK] - SMTP testbericht",
|
||||
"emailTestBody": "Dit is een testbericht",
|
||||
"emailTestBodyHtml": "<p>Dit is een testbericht</p>",
|
||||
"identityProviderLinkSubject": "Koppel {0}",
|
||||
"identityProviderLinkBody": "Iemand wil uw \"{1}\" account koppelen met \"{0}\" account van gebruiker {2}. Als u dit was, klik dan op de onderstaande link om de accounts te koppelen\n\n{3}\n\nDeze link zal over {5} vervallen.\n\nAls u de accounts niet wilt koppelen, negeer dan dit bericht. Als u accounts koppelt, dan kunt u bij {1} inloggen via {0}.",
|
||||
"identityProviderLinkBodyHtml": "<p>Iemand wil uw \"{1}\" account koppelen met \"{0}\" account van gebruiker {2}. Als u dit was, klik dan op de onderstaande link om de accounts te koppelen</p><p><a href=\"{3}\">Link om accounts te koppelen</a></p><p>Deze link zal over {5} vervallen.</p><p>Als u de accounts niet wilt koppelen, negeer dan dit bericht. Als u accounts koppelt, dan kunt u bij {1} inloggen via {0}.</p>",
|
||||
"passwordResetSubject": "Wijzig wachtwoord",
|
||||
"passwordResetBody": "Iemand verzocht de aanmeldgegevens van uw {2} account te wijzigen. Als u dit was, klik dan op de onderstaande koppeling om ze te wijzigen.\n\n{0}\n\nDe link en de code zullen binnen {3} vervallen.\n\nAls u uw aanmeldgegevens niet wilt wijzigen, negeer dan dit bericht en er zal niets gewijzigd worden.",
|
||||
"passwordResetBodyHtml": "<p>Iemand verzocht de aanmeldgegevens van uw {2} account te wijzigen. Als u dit was, klik dan op de onderstaande koppeling om ze te wijzigen.</p><p><a href=\"{0}\">Wijzig aanmeldgegevens</a></p><p>De link en de code zullen binnen {3} vervallen.</p><p>Als u uw aanmeldgegevens niet wilt wijzigen, negeer dan dit bericht en er zal niets gewijzigd worden.</p>",
|
||||
"executeActionsSubject": "Wijzig uw account",
|
||||
"executeActionsBody": "Uw beheerder heeft u verzocht uw {2} account te wijzigen. Klik op de onderstaande koppeling om dit proces te starten. \n\n{0}\n\nDeze link zal over {4} vervallen. \n\nAls u niet over dit verzoek op de hoogte was, negeer dan dit bericht om uw account ongewijzigd te laten.",
|
||||
"executeActionsBodyHtml": "<p>Uw beheerder heeft u verzocht uw {2} account te wijzigen. Klik op de onderstaande koppeling om dit proces te starten.</p><p><a href=\"{0}\">Link naar account wijziging</a></p><p>Deze link zal over {4} vervallen.</p><p>Als u niet over dit verzoek op de hoogte was, negeer dan dit bericht om uw account ongewijzigd te laten.</p>",
|
||||
"eventLoginErrorSubject": "Inlogfout",
|
||||
"eventLoginErrorBody": "Er is een foutieve inlogpoging gedetecteerd op uw account om {0} vanuit {1}. Als u dit niet was, neem dan contact op met de beheerder.",
|
||||
"eventLoginErrorBodyHtml": "<p>Er is een foutieve inlogpoging gedetecteerd op uw account om {0} vanuit {1}. Als u dit niet was, neem dan contact op met de beheerder.</p>",
|
||||
"eventRemoveTotpSubject": "OTP verwijderd",
|
||||
"eventRemoveTotpBody": "OTP is verwijderd van uw account om {0} vanuit {1}. Als u dit niet was, neem dan contact op met uw beheerder.",
|
||||
"eventRemoveTotpBodyHtml": "<p>OTP is verwijderd van uw account om {0} vanuit {1}. Als u dit niet was, neem dan contact op met uw beheerder.</p>",
|
||||
"eventUpdatePasswordSubject": "Wachtwoord gewijzigd",
|
||||
"eventUpdatePasswordBody": "Uw wachtwoord is gewijzigd om {0} door {1}. Als u dit niet was, neem dan contact op met uw beheerder.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Uw wachtwoord is gewijzigd om {0} door {1}. Als u dit niet was, neem dan contact op met uw beheerder.</p>",
|
||||
"eventUpdateTotpSubject": "OTP gewijzigd",
|
||||
"eventUpdateTotpBody": "OTP is gewijzigd voor uw account om {0} door {1}. Als u dit niet was, neem dan contact op met uw beheerder.",
|
||||
"eventUpdateTotpBodyHtml": "<p>OTP is gewijzigd voor uw account om {0} door {1}. Als u dit niet was, neem dan contact op met uw beheerder.</p>",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds": "seconden",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.1": "seconde",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes": "minuten",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.1": "minuut",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours": "uur",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.1": "uur",
|
||||
"linkExpirationFormatter.timePeriodUnit.days": "dagen",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.1": "dag"
|
||||
},
|
||||
"no": {
|
||||
"emailVerificationSubject": "Bekreft e-postadresse",
|
||||
"emailVerificationBody": "Noen har opprettet en {2} konto med denne e-postadressen. Hvis dette var deg, klikk på lenken nedenfor for å bekrefte e-postadressen din\n\n{0}\n\nDenne lenken vil utløpe om {1} minutter.\n\nHvis du ikke opprettet denne kontoen, vennligst ignorer denne meldingen.",
|
||||
"emailVerificationBodyHtml": "<p>Noen har opprettet en {2} konto med denne e-postadressen. Hvis dette var deg, klikk på lenken nedenfor for å bekrefte e-postadressen din</p><p><a href=\"{0}\">{0}</a></p><p>Denne lenken vil utløpe om {1} minutter.</p><p>Hvis du ikke opprettet denne kontoen, vennligst ignorer denne meldingen.</p>",
|
||||
"identityProviderLinkSubject": "Lenke {0}",
|
||||
"identityProviderLinkBody": "Noen vil koble din <b>{1}</b> konto med <b>{0}</b> konto til bruker {2}. Hvis dette var deg, klikk på lenken nedenfor for å koble kontoene\n\n{3}\n\nDenne lenken vil utløpe om {4} minutter\n\nHvis du ikke vil koble kontoene, vennligst ignorer denne meldingen. Hvis du kobler kontoene sammen vil du kunne logge inn til {1} gjennom {0}.",
|
||||
"identityProviderLinkBodyHtml": "<p>Noen vil koble din <b>{1}</b> konto med <b>{0}</b> konto til bruker {2}. Hvis dette var deg, klikk på lenken nedenfor for å koble kontoene.</p><p><a href=\"{3}\">{3}</a></p><p>Denne lenken vil utløpe om {4} minutter.</p><p>Hvis du ikke vil koble kontoene, vennligst ignorer denne meldingen. Hvis du kobler kontoene sammen vil du kunne logge inn til {1} gjennom {0}.</p>",
|
||||
"passwordResetSubject": "Tilbakestill passord",
|
||||
"passwordResetBody": "Noen har bedt om å endre innloggingsdetaljene til din konto {2}. Hvis dette var deg, klikk på lenken nedenfor for å tilbakestille dem.\n\n{0}\n\nDenne lenken vil utløpe om {1} minutter.\n\nHvis du ikke vil tilbakestille din innloggingsdata, vennligst ignorer denne meldingen og ingenting vil bli endret.",
|
||||
"passwordResetBodyHtml": "<p>Noen har bedt om å endre innloggingsdetaljene til din konto {2}. Hvis dette var deg, klikk på lenken nedenfor for å tilbakestille dem.</p><p><a href=\"{0}\">{0}</a></p><p>Denne lenken vil utløpe om {1} minutter.</p><p>Hvis du ikke vil tilbakestille din innloggingsdata, vennligst ignorer denne meldingen og ingenting vil bli endret.</p>",
|
||||
"executeActionsSubject": "Oppdater kontoen din",
|
||||
"executeActionsBody": "Administrator har anmodet at du oppdaterer din {2} konto. Klikk på lenken nedenfor for å starte denne prosessen\n\n{0}\n\nDenne lenken vil utløpe om {1} minutter.\n\nHvis du ikke var klar over at administrator har bedt om dette, vennligst ignorer denne meldingen og ingenting vil bli endret.",
|
||||
"executeActionsBodyHtml": "<p>Administrator har anmodet at du oppdaterer din {2} konto. Klikk på linken nedenfor for å starte denne prosessen.</p><p><a href=\"{0}\">{0}</a></p><p>Denne lenken vil utløpe om {1} minutter.</p><p>Hvis du ikke var klar over at administrator har bedt om dette, ignorer denne meldingen og ingenting vil bli endret. </p>",
|
||||
"eventLoginErrorSubject": "Innlogging feilet",
|
||||
"eventLoginErrorBody": "Et mislykket innloggingsforsøk ble oppdaget på din konto på {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.",
|
||||
"eventLoginErrorBodyHtml": "<p>Et mislykket innloggingsforsøk ble oppdaget på din konto på {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.</p>",
|
||||
"eventRemoveTotpSubject": "Fjern engangskode",
|
||||
"eventRemoveTotpBody": "Engangskode ble fjernet fra kontoen din på {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.",
|
||||
"eventRemoveTotpBodyHtml": "<p>Engangskode ble fjernet fra kontoen din på {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.</p>",
|
||||
"eventUpdatePasswordSubject": "Oppdater passord",
|
||||
"eventUpdatePasswordBody": "Ditt passord ble endret i {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Ditt passord ble endret i {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. </p>",
|
||||
"eventUpdateTotpSubject": "Oppdater engangskode",
|
||||
"eventUpdateTotpBody": "Engangskode ble oppdatert for kontoen din på {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.",
|
||||
"eventUpdateTotpBodyHtml": "<p>Engangskode ble oppdatert for kontoen din på {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. </p>"
|
||||
},
|
||||
"pl": {
|
||||
"emailVerificationSubject": "Zweryfikuj email",
|
||||
"emailVerificationBody": "Ktoś utworzył już konto {2} z tym adresem e-mail. Jeśli to Ty, kliknij poniższy link, aby zweryfikować swój adres e-mail \n\n{0}\n\nLink ten wygaśnie w ciągu {3}.\n\nJeśli nie utworzyłeś tego konta, po prostu zignoruj tę wiadomość.",
|
||||
"emailVerificationBodyHtml": "<p>Ktoś utworzył już konto {2} z tym adresem e-mail. Jeśli to Ty, kliknij <a href=\"{0}\">ten link</a> aby zweryfikować swój adres e-mail</p><p>Link ten wygaśnie w ciągu {3}</p><p>Jeśli nie utworzyłeś tego konta, po prostu zignoruj tę wiadomość.</p>",
|
||||
"emailTestSubject": "[KEYCLOAK] - wiadomość testowa SMTP",
|
||||
"emailTestBody": "To jest wiadomość testowa",
|
||||
"emailTestBodyHtml": "<p>To jest wiadomość testowa</p>",
|
||||
"identityProviderLinkSubject": "Link {0}",
|
||||
"identityProviderLinkBody": "Ktoś chce połączyć Twoje konto \"{1}\" z kontem \"{0}\" użytkownika {2}. Jeśli to Ty, kliknij poniższy link by połączyć konta\n\n{3}\n\nTen link wygaśnie w ciągu {5}.\n\nJeśli nie chcesz połączyć konta to zignoruj tę wiadomość. Jeśli połączysz konta, będziesz mógł się zalogować na {1} przez {0}.",
|
||||
"identityProviderLinkBodyHtml": "<p>Ktoś chce połączyć Twoje konto <b>{1}</b> z kontem <b>{0}</b> użytkownika {2}. Jeśli to Ty, kliknij <a href=\"{3}\">ten link</a> by połączyć konta.</p><p>Ten link wygaśnie w ciągu {5}.</p><p>Jeśli nie chcesz połączyć konta to zignoruj tę wiadomość. Jeśli połączysz konta, będziesz mógł się zalogować na {1} przez {0}.</p>",
|
||||
"passwordResetSubject": "Zresetuj hasło",
|
||||
"passwordResetBody": "Ktoś właśnie poprosił o zmianę danych logowania Twojego konta {2}. Jeśli to Ty, kliknij poniższy link, aby je zresetować.\n\n{0}\n\nTen link i kod stracą ważność w ciągu {3}.\n\nJeśli nie chcesz zresetować swoich danych logowania, po prostu zignoruj tę wiadomość i nic się nie zmieni.",
|
||||
"passwordResetBodyHtml": "<p>Ktoś właśnie poprosił o zmianę poświadczeń Twojego konta {2}. Jeśli to Ty, kliknij poniższy link, aby je zresetować.</p><p><a href=\"{0}\">Link do resetowania poświadczeń</a></p><p>Ten link wygaśnie w ciągu {3}.</p><p>Jeśli nie chcesz resetować swoich poświadczeń, po prostu zignoruj tę wiadomość i nic się nie zmieni.</p>",
|
||||
"executeActionsSubject": "Zaktualizuj swoje konto",
|
||||
"executeActionsBody": "Administrator właśnie zażądał aktualizacji konta {2} poprzez wykonanie następujących działań: {3}. Kliknij poniższy link, aby rozpocząć ten proces.\n\n{0}\n\nTen link wygaśnie w ciągu {4}.\n\nJeśli nie masz pewności, że administrator tego zażądał, po prostu zignoruj tę wiadomość i nic się nie zmieni.",
|
||||
"executeActionsBodyHtml": "<p>Administrator właśnie zażądał aktualizacji konta {2} poprzez wykonanie następujących działań: {3}. Kliknij <a href=\"{0}\">ten link</a>, aby rozpocząć proces.</p><p>Link ten wygaśnie w ciągu {4}.</p><p>Jeśli nie masz pewności, że administrator tego zażądał, po prostu zignoruj tę wiadomość i nic się nie zmieni.</p>",
|
||||
"eventLoginErrorSubject": "Błąd logowania",
|
||||
"eventLoginErrorBody": "Nieudana próba logowania została wykryta na Twoim koncie {0} z {1}. Jeśli to nie Ty, skontaktuj się z administratorem.",
|
||||
"eventLoginErrorBodyHtml": "<p>Nieudana próba logowania została wykryta na Twoim koncie {0} z {1}. Jeśli to nie Ty, skontaktuj się z administratorem.</p>",
|
||||
"eventRemoveTotpSubject": "Usuń hasło jednorazowe (OTP)",
|
||||
"eventRemoveTotpBody": "Hasło jednorazowe (OTP) zostało usunięte z Twojego konta w {0} z {1}. Jeśli to nie Ty, skontaktuj się z administratorem.",
|
||||
"eventRemoveTotpBodyHtml": "<p>Hasło jednorazowe (OTP) zostało usunięte z Twojego konta w {0} z {1}. Jeśli to nie Ty, skontaktuj się z administratorem.</p>",
|
||||
"eventUpdatePasswordSubject": "Aktualizuj hasło",
|
||||
"eventUpdatePasswordBody": "Twoje hasło zostało zmienione {0} z {1}. Jeśli to nie Ty, skontaktuj się z administratorem.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Twoje hasło zostało zmienione {0} z {1}. Jeśli to nie Ty, skontaktuj się z administratorem.</p>",
|
||||
"eventUpdateTotpSubject": "Aktualizuj hasło jednorazowe (OTP)",
|
||||
"eventUpdateTotpBody": "Hasło jednorazowe (OTP) zostało zaktualizowane na Twoim koncie {0} z {1}. Jeśli to nie Ty, skontaktuj się z administratorem.",
|
||||
"eventUpdateTotpBodyHtml": "<p>Hasło jednorazowe (OTP) zostało zaktualizowane na Twoim koncie {0} z {1}. Jeśli to nie Ty, skontaktuj się z administratorem.</p>",
|
||||
"requiredAction.CONFIGURE_TOTP": "Konfiguracja hasła jednorazowego (OTP)",
|
||||
"requiredAction.terms_and_conditions": "Regulamin",
|
||||
"requiredAction.UPDATE_PASSWORD": "Aktualizacja hasła",
|
||||
"requiredAction.UPDATE_PROFILE": "Aktualizacja profilu",
|
||||
"requiredAction.VERIFY_EMAIL": "Weryfikacja adresu e-mail",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds": "sekund",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.1": "sekunda",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.2": "sekundy",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.3": "sekundy",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.4": "sekundy",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes": "minut",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.1": "minuta",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.2": "minuty",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.3": "minuty",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.4": "minuty",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours": "godzin",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.1": "godzina",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.2": "godziny",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.3": "godziny",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.4": "godziny",
|
||||
"linkExpirationFormatter.timePeriodUnit.days": "dni",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.1": "dzień",
|
||||
"emailVerificationBodyCode": "Potwierdź swój adres e-mail wprowadzając następujący kod.\n\n{0}\n\n.",
|
||||
"emailVerificationBodyCodeHtml": "<p>Potwierdź swój adres e-mail, wprowadzając następujący kod.</p><p><b>{0}</b></p>"
|
||||
},
|
||||
"pt-BR": {
|
||||
"emailVerificationSubject": "Verificação de e-mail",
|
||||
"emailVerificationBody": "Alguém criou uma conta {2} com este endereço de e-mail. Se foi você, clique no link abaixo para verificar o seu endereço de email\n\n{0}\n\nEste link irá expirar dentro de {3}.\n\nSe não foi você que criou esta conta, basta ignorar esta mensagem.",
|
||||
"emailVerificationBodyHtml": "<p>Alguém criou uma conta {2} com este endereço de e-mail. Se foi você, clique no link abaixo para verificar o seu endereço de email</p><p><a href=\"{0}\">{0}</a></p><p>Este link irá expirar dentro de {3}.</p><p>Se não foi você que criou esta conta, basta ignorar esta mensagem.</p>",
|
||||
"emailTestSubject": "[KEYCLOAK] - SMTP mensagem de teste",
|
||||
"emailTestBody": "Esta é uma mensagem de teste",
|
||||
"emailTestBodyHtml": "<p>Esta é uma mensagem de teste</p>",
|
||||
"identityProviderLinkSubject": "Vincular {0}",
|
||||
"identityProviderLinkBody": "Alguém quer vincular sua conta \"{1}\" com a conta \"{0}\" do usuário {2} . Se foi você, clique no link abaixo para vincular as contas.\n\n{3}\n\nEste link irá expirar em {5}.\n\nSe você não quer vincular a conta, apenas ignore esta mensagem. Se você vincular as contas, você será capaz de logar em {1} atrávés de {0}.",
|
||||
"identityProviderLinkBodyHtml": "<p>Alguém quer vincular sua conta <b>{1}</b> com a conta <b>{0}</b> do usuário {2} . Se foi você, clique no link abaixo para vincular as contas.</p><p><a href=\"{3}\">{3}</a></p><p>Este link irá expirar em {5}.</p><p>Se você não quer vincular a conta, apenas ignore esta mensagem. Se você vincular as contas, você será capaz de logar em {1} atrávés de {0}.</p>",
|
||||
"passwordResetSubject": "Redefinição de senha",
|
||||
"passwordResetBody": "Alguém solicitou uma alteração de senha da sua conta {2}. Se foi você, clique no link abaixo para redefini-la.\n\n{0}\n\nEste link e código expiram em {3}.\n\nSe você não deseja redefinir sua senha, apenas ignore esta mensagem e nada será alterado.",
|
||||
"passwordResetBodyHtml": "<p>Alguém solicitou uma alteração de senha da sua conta {2}. Se foi você, clique no link abaixo para redefini-la.</p><p><a href=\"{0}\">Link para redefinir a senha</a></p><p>Este link irá expirar em {3}.</p><p>Se você não deseja redefinir sua senha, apenas ignore esta mensagem e nada será alterado.</p>",
|
||||
"executeActionsSubject": "Atualização de conta",
|
||||
"executeActionsBody": "O administrador solicitou que você atualize sua conta {2} executando a(s) seguinte(s) ação(ões): {3}. Clique no link abaixo para iniciar o processo.\n\n{0}\n\nEste link irá expirar em {4}.\n\nSe você não tem conhecimento de que o administrador solicitou isso, basta ignorar esta mensagem e nada será alterado.",
|
||||
"executeActionsBodyHtml": "<p>O administrador solicitou que você atualize sua conta {2} executando a(s) seguinte(s) ação(ões): {3}. Clique no link abaixo para iniciar o processo.</p><p><a href=\"{0}\">Link to account update</a></p><p>Este link irá expirar em {4}.</p><p>Se você não tem conhecimento de que o administrador solicitou isso, basta ignorar esta mensagem e nada será alterado.</p>",
|
||||
"eventLoginErrorSubject": "Erro de login",
|
||||
"eventLoginErrorBody": "Uma tentativa de login mal sucedida para a sua conta foi detectada em {0} de {1}. Se não foi você, por favor, entre em contato com um administrador.",
|
||||
"eventLoginErrorBodyHtml": "<p>Uma tentativa de login mal sucedida para a sua conta foi detectada em {0} de {1}. Se não foi você, por favor, entre em contato com um administrador.</p>",
|
||||
"eventRemoveTotpSubject": "Remover OTP",
|
||||
"eventRemoveTotpBody": "OTP foi removido da sua conta em {0} de {1}. Se não foi você, por favor, entre em contato com um administrador.",
|
||||
"eventRemoveTotpBodyHtml": "<p>OTP foi removido da sua conta em {0} de {1}. Se não foi você, por favor, entre em contato com um administrador.</p>",
|
||||
"eventUpdatePasswordSubject": "Atualização de senha",
|
||||
"eventUpdatePasswordBody": "Sua senha foi alterada em {0} de {1}. Se não foi você, por favor, entre em contato com um administrador.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Sua senha foi alterada em {0} de {1}. Se não foi você, por favor, entre em contato com um administrador.</p>",
|
||||
"eventUpdateTotpSubject": "Atualização OTP",
|
||||
"eventUpdateTotpBody": "OTP foi atualizado para a sua conta em {0} de {1}. Se não foi você, por favor, entre em contato com um administrador.",
|
||||
"eventUpdateTotpBodyHtml": "<p>OTP foi atualizado para a sua conta em {0} de {1}. Se não foi você, por favor, entre em contato com um administrador.</p>",
|
||||
"requiredAction.CONFIGURE_TOTP": "Configurar OTP",
|
||||
"requiredAction.terms_and_conditions": "Termos e Condições",
|
||||
"requiredAction.UPDATE_PASSWORD": "Atualizar Senha",
|
||||
"requiredAction.UPDATE_PROFILE": "Atualizar Perfil",
|
||||
"requiredAction.VERIFY_EMAIL": "Verificar E-mail",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds": "segundos",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.1": "segundo",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes": "minutos",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.1": "minuto",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours": "horas",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.1": "hora",
|
||||
"linkExpirationFormatter.timePeriodUnit.days": "dias",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.1": "dia",
|
||||
"emailVerificationBodyCode": "Verifique seu endereço de e-mail digitando o seguinte código.\n\n{0}\n\n.",
|
||||
"emailVerificationBodyCodeHtml": "<p>Verifique seu endereço de e-mail digitando o seguinte código.</p><p><b>{0}</b></p>"
|
||||
},
|
||||
"ru": {
|
||||
"emailVerificationSubject": "Подтверждение E-mail",
|
||||
"emailVerificationBody": "Кто-то создал учетную запись {2} с этим E-mail. Если это были Вы, нажмите на следующую ссылку для подтверждения вашего email\n\n{0}\n\nЭта ссылка устареет через {1} минут.\n\nЕсли Вы не создавали учетную запись, просто проигнорируйте это письмо.",
|
||||
"emailVerificationBodyHtml": "<p>Кто-то создал учетную запись {2} с этим E-mail. Если это были Вы, нажмите по ссылке для подтверждения вашего E-mail</p><p><a href=\"{0}\">{0}</a></p><p>Эта ссылка устареет через {1} минут.</p><p>Если Вы не создавали учетную запись, просто проигнорируйте это письмо.</p>",
|
||||
"identityProviderLinkSubject": "Ссылка {0}",
|
||||
"identityProviderLinkBody": "Кто-то хочет связать вашу учетную запись \"{1}\" с \"{0}\" учетной записью пользователя {2} . Если это были Вы, нажмите по следующей ссылке, чтобы связать учетные записи\n\n{3}\n\nЭта ссылка устареет через {4} минут.\n\nЕсли это не хотите объединять учетные записи, просто проигнориуйте это письмо. После объединения учетных записей Вы можете войти в {1} через {0}.",
|
||||
"identityProviderLinkBodyHtml": "<p>Кто-то хочет связать вашу учетную запись <b>{1}</b> с <b>{0}</b> учетной записью пользователя {2} . Если это были Вы, нажмите по следующей ссылке, чтобы связать учетные записи</p><p><a href=\"{3}\">{3}</a></p><p>Эта ссылка устареет через {4} минут.</p><p>Если это не хотите объединять учетные записи, просто проигнориуйте это письмо. После объединения учетных записей Вы можете войти в {1} через {0}.</p>",
|
||||
"passwordResetSubject": "Сброс пароля",
|
||||
"passwordResetBody": "Кто-то только что запросил изменение пароля от Вашей учетной записи {2}. Если это были Вы, нажмите на следующую ссылку, чтобы сбросить его.\n\n{0}\n\nЭта ссылка устареет через {1} минут.\n\nЕсли Вы не хотите сбрасывать пароль, просто проигнорируйте это письмо.",
|
||||
"passwordResetBodyHtml": "<p>Кто-то только что запросил изменение пароля от Вашей учетной записи {2}. Если это были Вы, нажмите на следующую ссылку, чтобы сбросить его.</p><p><a href=\"{0}\">{0}</a></p><p>Эта ссылка устареет через {1} минут.</p><p>Если Вы не хотите сбрасывать пароль, просто проигнорируйте это письмо и ничего не изменится.</p>",
|
||||
"executeActionsSubject": "Обновление Вашей учетной записи",
|
||||
"executeActionsBody": "Администратор просит Вас обновить данные Вашей учетной записи {2}. Нажмите по следующей ссылке чтобы начать этот процесс.\n\n{0}\n\nЭта ссылка устареет через {1} минут.\n\nЕсли у вас есть подозрения, что администратор не мог сделать такой запрос, просто проигнорируйте это письмо.",
|
||||
"executeActionsBodyHtml": "<p>Администратор просит Вас обновить данные Вашей учетной записи {2}. Нажмите по следующей ссылке чтобы начать этот процесс.</p><p><a href=\"{0}\">{0}</a></p><p>Эта ссылка устареет через {1} минут.</p><p>Если у вас есть подозрения, что администратор не мог сделать такой запрос, просто проигнорируйте это письмо.</p>",
|
||||
"eventLoginErrorSubject": "Ошибка входа",
|
||||
"eventLoginErrorBody": "Была зафиксирована неудачная попытка входа в Вашу учетную запись {0} с {1}. Если это были не Вы, пожалуйста, свяжитесь с администратором.",
|
||||
"eventLoginErrorBodyHtml": "<p>Была зафиксирована неудачная попытка входа в Вашу учетную запись {0} с {1}. Если это были не Вы, пожалуйста, свяжитесь с администратором.</p>",
|
||||
"eventRemoveTotpSubject": "Удалить OTP",
|
||||
"eventRemoveTotpBody": "OTP был удален из вашей учетной записи {0} c {1}. Если это были не Вы, пожалуйста, свяжитесь с администратором.",
|
||||
"eventRemoveTotpBodyHtml": "<p>OTP был удален из вашей учетной записи {0} c {1}. Если это были не Вы, пожалуйста, свяжитесь с администратором.</p>",
|
||||
"eventUpdatePasswordSubject": "Обновление пароля",
|
||||
"eventUpdatePasswordBody": "Ваш пароль был изменен в {0} с {1}. Если это были не Вы, пожалуйста, свяжитесь с администратором.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Ваш пароль был изменен в {0} с {1}. Если это были не Вы, пожалуйста, свяжитесь с администратором.</p>",
|
||||
"eventUpdateTotpSubject": "Обновление OTP",
|
||||
"eventUpdateTotpBody": "OTP был обновлен в вашей учетной записи {0} с {1}. Если это были не Вы, пожалуйста, свяжитесь с администратором.",
|
||||
"eventUpdateTotpBodyHtml": "<p>OTP был обновлен в вашей учетной записи {0} с {1}. Если это были не Вы, пожалуйста, свяжитесь с администратором.</p>"
|
||||
},
|
||||
"sk": {
|
||||
"emailVerificationSubject": "Overenie e-mailu",
|
||||
"emailVerificationBody": "Niekto vytvoril účet {2} s touto e-mailovou adresou. Ak ste to vy, kliknite na nižšie uvedený odkaz a overte svoju e-mailovú adresu \n\n{0}\n\nTento odkaz uplynie do {1} minút.\n\nAk ste tento účet nevytvorili, ignorujte túto správu.",
|
||||
"emailVerificationBodyHtml": "<p>Niekto vytvoril účet {2} s touto e-mailovou adresou. Ak ste to vy, kliknite na nižšie uvedený odkaz na overenie svojej e-mailovej adresy.</p><p><a href=\"{0}\"> Odkaz na overenie e-mailovej adresy </a></p><p>Platnosť odkazu vyprší za {1} minút.</p><p> Ak ste tento účet nevytvorili, ignorujte túto správu.</p>",
|
||||
"emailTestSubject": "[KEYCLOAK] - Testovacia správa SMTP",
|
||||
"emailTestBody": "Toto je skúšobná správa",
|
||||
"emailTestBodyHtml": "<p>Toto je skúšobná správa</p>",
|
||||
"identityProviderLinkSubject": "Odkaz {0}",
|
||||
"identityProviderLinkBody": "Niekto chce prepojiť váš účet \"{1}\" s účtom {0}\"používateľa {2}. Ak ste to vy, kliknutím na odkaz nižšie prepojte účty. \n\n{3}\n\nTento odkaz uplynie do {4} minút.\n\nAk nechcete prepojiť účet, jednoducho ignorujte túto správu , Ak prepájate účty, budete sa môcť prihlásiť do {1} až {0}.",
|
||||
"identityProviderLinkBodyHtml": "<p>Niekto chce prepojiť váš účet <b>{1}</b> s účtom <b>{0}</b> používateľa {2}. Ak ste to vy, kliknutím na odkaz nižšie prepojte účty</p><p><a href=\"{3}\">Odkaz na potvrdenie prepojenia účtu </a></p><p> Platnosť tohto odkazu vyprší v rámci {4} minút.</p><p>Ak nechcete prepojiť účet, ignorujte túto správu. Ak prepojujete účty, budete sa môcť prihlásiť do {1} až {0}.</p>",
|
||||
"passwordResetSubject": "Obnovenie hesla",
|
||||
"passwordResetBody": "Niekto požiadal, aby ste zmenili svoje poverenia účtu {2}. Ak ste to vy, kliknite na odkaz uvedený nižšie, aby ste ich vynulovali.\n\n{0}\n\nTento odkaz a kód uplynie do {1} minút.\n\nAk nechcete obnoviť svoje poverenia , ignorujte túto správu a nič sa nezmení.",
|
||||
"passwordResetBodyHtml": "<p>Niekto požiadal, aby ste zmenili svoje poverenia účtu {2}. Ak ste to vy, kliknutím na odkaz nižšie ich resetujte.</p><p><a href=\"{0}\">Odkaz na obnovenie poverení </a></p><p>Platnosť tohto odkazu vyprší v priebehu {1} minút.</p><p>Ak nechcete obnoviť svoje poverenia, ignorujte túto správu a nič sa nezmení.</p>",
|
||||
"executeActionsSubject": "Aktualizujte svoj účet",
|
||||
"executeActionsBody": "Váš administrátor práve požiadal o aktualizáciu vášho účtu {2} vykonaním nasledujúcich akcií: {3}. Kliknutím na odkaz uvedený nižšie spustíte tento proces.\n\n{0}\n\nTento odkaz vyprší za {1} minúty.\n\nAk si nie ste vedomý, že váš adminstrátor o toto požiadal, ignorujte túto správu a nič bude zmenené.",
|
||||
"executeActionsBodyHtml": "<p>Váš správca práve požiadal o aktualizáciu vášho účtu {2} vykonaním nasledujúcich akcií: {3}. Kliknutím na odkaz uvedený nižšie spustíte tento proces.</p><p><a href=\"{0}\"> Odkaz na aktualizáciu účtu </a></p><p> Platnosť tohto odkazu uplynie do {1} minúty.</p><p> Ak si nie ste vedomí, že váš adminstrátor o toto požiadal, ignorujte túto správu a nič sa nezmení.</p>",
|
||||
"eventLoginErrorSubject": "Chyba prihlásenia",
|
||||
"eventLoginErrorBody": "Bol zistený neúspešný pokus o prihlásenie do vášho účtu v {0} z {1}. Ak ste to neboli vy, obráťte sa na administrátora.",
|
||||
"eventLoginErrorBodyHtml": "<p>Bol zistený neúspešný pokus o prihlásenie vášho účtu na {0} z {1}. Ak ste to neboli vy, kontaktujte administrátora.</p>",
|
||||
"eventRemoveTotpSubject": "Odstrániť TOTP",
|
||||
"eventRemoveTotpBody": "OTP bol odstránený z vášho účtu dňa {0} z {1}. Ak ste to neboli vy, obráťte sa na administrátora.",
|
||||
"eventRemoveTotpBodyHtml": "<p>OTP bol odstránený z vášho účtu dňa {0} z {1}. Ak ste to neboli vy, kontaktujte administrátora.</p>",
|
||||
"eventUpdatePasswordSubject": "Aktualizovať heslo",
|
||||
"eventUpdatePasswordBody": "Vaše heslo bolo zmenené na {0} z {1}. Ak ste to neboli vy, obráťte sa na administrátora.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Vaše heslo bolo zmenené na {0} z {1}. Ak ste to neboli vy, kontaktujte administrátora.</p>",
|
||||
"eventUpdateTotpSubject": "Aktualizácia TOTP",
|
||||
"eventUpdateTotpBody": "TOTP bol aktualizovaný pre váš účet na {0} z {1}. Ak ste to neboli vy, obráťte sa na administrátora.",
|
||||
"eventUpdateTotpBodyHtml": "<p>TOTP bol aktualizovaný pre váš účet dňa {0} z {1}. Ak ste to neboli vy, kontaktujte administrátora.</p>",
|
||||
"requiredAction.CONFIGURE_TOTP": "Konfigurácia OTP",
|
||||
"requiredAction.terms_and_conditions": "Zmluvné podmienky",
|
||||
"requiredAction.UPDATE_PASSWORD": "Aktualizovať heslo",
|
||||
"requiredAction.UPDATE_PROFILE": "Aktualizovať profil",
|
||||
"requiredAction.VERIFY_EMAIL": "Overiť e-mail",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds": "sekundy",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.1": "sekunda",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes": "minuty",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.1": "minúta",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours": "hodiny",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.1": "hodina",
|
||||
"linkExpirationFormatter.timePeriodUnit.days": "dni",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.1": "deň "
|
||||
},
|
||||
"sv": {
|
||||
"emailVerificationSubject": "Verifiera e-post",
|
||||
"emailVerificationBody": "Någon har skapat ett {2} konto med den här e-postadressen. Om det var du, klicka då på länken nedan för att verifiera din e-postadress\n\n{0}\n\nDen här länken kommer att upphöra inom {1} minuter.\n\nOm det inte var du som skapade det här kontot, ignorera i så fall det här meddelandet.",
|
||||
"emailVerificationBodyHtml": "<p>Någon har skapat ett {2} konto med den här e-postadressen. Om det var du, klicka då på länken nedan för att verifiera din e-postadress</p><p><a href=\"{0}\">{0}</a></p><p>Den här länken kommer att upphöra inom {1} minuter.</p><p>Om det inte var du som skapade det här kontot, ignorera i så fall det här meddelandet.</p>",
|
||||
"identityProviderLinkSubject": "Länk {0}",
|
||||
"identityProviderLinkBody": "Någon vill länka ditt \"{1}\" konto med \"{0}\" kontot tillhörande användaren {2} . Om det var du, klicka då på länken nedan för att länka kontona\n\n{3}\n\nDen här länken kommer att upphöra inom {4} minuter.\n\nOm du inte vill länka kontot, ignorera i så fall det här meddelandet. Om du länkar kontona, så kan du logga in till {1} genom {0}.",
|
||||
"identityProviderLinkBodyHtml": "<p>Någon vill länka ditt <b>{1}</b> konto med <b>{0}</b> kontot tillhörande användaren {2} . Om det var du, klicka då på länken nedan för att länka kontona</p><p><a href=\"{3}\">{3}</a></p><p>Den här länken kommer att upphöra inom {4} minuter.</p><p>Om du inte vill länka kontot, ignorera i så fall det här meddelandet. Om du länkar kontona, så kan du logga in till {1} genom {0}.</p>",
|
||||
"passwordResetSubject": "Återställ lösenord",
|
||||
"passwordResetBody": "Någon har precis bett om att ändra användaruppgifter för ditt konto {2}. Om det var du, klicka då på länken nedan för att återställa dem.\n\n{0}\n\nDen här länken och koden kommer att upphöra inom {1} minuter.\n\nOm du inte vill återställa dina kontouppgifter, ignorera i så fall det här meddelandet så kommer inget att ändras.",
|
||||
"passwordResetBodyHtml": "<p>Någon har precis bett om att ändra användaruppgifter för ditt konto {2}. Om det var du, klicka då på länken nedan för att återställa dem.</p><p><a href=\"{0}\">{0}</a></p><p>Den här länken och koden kommer att upphöra inom {1} minuter.</p><p>Om du inte vill återställa dina kontouppgifter, ignorera i så fall det här meddelandet så kommer inget att ändras.</p>",
|
||||
"executeActionsSubject": "Uppdatera ditt konto",
|
||||
"executeActionsBody": "Din administratör har precis bett om att du skall uppdatera ditt {2} konto. Klicka på länken för att påbörja processen.\n\n{0}\n\nDen här länken kommer att upphöra inom {1} minuter.\n\nOm du är omedveten om att din administratör har bett om detta, ignorera i så fall det här meddelandet så kommer inget att ändras.",
|
||||
"executeActionsBodyHtml": "<p>Din administratör har precis bett om att du skall uppdatera ditt {2} konto. Klicka på länken för att påbörja processen.</p><p><a href=\"{0}\">{0}</a></p><p>Den här länken kommer att upphöra inom {1} minuter.</p><p>Om du är omedveten om att din administratör har bett om detta, ignorera i så fall det här meddelandet så kommer inget att ändras.</p>",
|
||||
"eventLoginErrorSubject": "Inloggningsfel",
|
||||
"eventLoginErrorBody": "Ett misslyckat inloggningsförsök har upptäckts på ditt konto på {0} från {1}. Om det inte var du, vänligen kontakta i så fall en administratör.",
|
||||
"eventLoginErrorBodyHtml": "<p>Ett misslyckat inloggningsförsök har upptäckts på ditt konto den {0} från {1}. Om det inte var du, vänligen kontakta i så fall en administratör.</p>",
|
||||
"eventRemoveTotpSubject": "Ta bort OTP",
|
||||
"eventRemoveTotpBody": "OTP togs bort från ditt konto den {0} från {1}. Om det inte var du, vänligen kontakta i så fall en administratör.",
|
||||
"eventRemoveTotpBodyHtml": "<p>OTP togs bort från ditt konto den {0} från {1}. Om det inte var du, vänligen kontakta i så fall en administratör.</p>",
|
||||
"eventUpdatePasswordSubject": "Uppdatera lösenord",
|
||||
"eventUpdatePasswordBody": "Ditt lösenord ändrades den {0} från {1}. Om det inte var du, vänligen kontakta i så fall en administratör.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Ditt lösenord ändrades den {0} från {1}. Om det inte var du, vänligen kontakta i så fall en administratör.</p>",
|
||||
"eventUpdateTotpSubject": "Uppdatera OTP",
|
||||
"eventUpdateTotpBody": "OTP uppdaterades för ditt konto den {0} från {1}. Om det inte var du, vänligen kontakta i så fall en administratör.",
|
||||
"eventUpdateTotpBodyHtml": "<p>OTP uppdaterades för ditt konto den {0} från {1}. Om det inte var du, vänligen kontakta i så fall en administratör.</p>"
|
||||
},
|
||||
"tr": {
|
||||
"emailVerificationSubject": "E-postayı doğrula",
|
||||
"emailVerificationBody": "Birisi bu e-posta adresiyle bir {2} hesap oluşturdu. Bu sizseniz, e-posta adresinizi doğrulamak için aşağıdaki bağlantıya tıklayın\n\n{0}\n\nBu bağlantı {3} içinde sona erecek.\n\nBu hesabı oluşturmadıysanız, sadece bu iletiyi yoksayınız.",
|
||||
"emailVerificationBodyHtml": "<p>Birisi bu e-posta adresiyle bir {2} hesap oluşturdu. Bu sizseniz, e-posta adresinizi doğrulamak için aşağıdaki bağlantıyı tıklayın.</p><p><a href=\"{0}\">E-posta adresi doğrulama adresi</a></p><p>Bu bağlantının süresi {3} içerisinde sona erecek.</p><p>Bu hesabı siz oluşturmadıysanız, bu mesajı göz ardı edin.</p>",
|
||||
"emailTestSubject": "[KEYCLOAK] - SMTP test mesajı",
|
||||
"emailTestBody": "Bu bir test mesajı",
|
||||
"emailTestBodyHtml": "<p>Bu bir test mesajı</p>",
|
||||
"identityProviderLinkSubject": "Link {0}",
|
||||
"identityProviderLinkBody": "Birisi \"{1}\" hesabınızı \"{0}\" kullanıcı hesabı {2} ile bağlamak istiyor. Bu sizseniz, hesapları bağlamak için aşağıdaki bağlantıyı tıklayın:\n\n{3}\n\nBu bağlantı {5} içinde sona erecek.\n\nHesabınızı bağlamak istemiyorsanız bu mesajı göz ardı edin. Hesapları bağlarsanız, {1} ile {0} arasında oturum açabilirsiniz.",
|
||||
"identityProviderLinkBodyHtml": "<p>Birisi <b> {1} </ b> hesabınızı {2} kullanıcısı <b> {0} </ b> hesabına bağlamak istiyor. Bu sizseniz, bağlantı vermek için aşağıdaki bağlantıyı tıklayın</p><p><a href=\"{3}\">Hesap bağlantısını onaylamak için bağlantı</a></p><p>Bu bağlantının süresi {5} içerisinde sona erecek.</p><p>Hesabı bağlamak istemiyorsanız, bu mesajı göz ardı edin. Hesapları bağlarsanız, {1} ile {0} arasında oturum açabilirsiniz.</p>",
|
||||
"passwordResetSubject": "Şifreyi sıfırla",
|
||||
"passwordResetBody": "Birisi, {2} hesabınızın kimlik bilgilerini değiştirmeyi istedi.Bu sizseniz, sıfırlamak için aşağıdaki bağlantıyı tıklayın.\n\n{0}\n\nBu bağlantı ve kod {3} içinde sona erecek.\n\nFakat bilgilerinizi sıfırlamak istemiyorsanız, Sadece bu mesajı görmezden gelin ve hiçbir şey değişmeyecek.",
|
||||
"passwordResetBodyHtml": "<p>Birisi, {2} hesabınızın kimlik bilgilerini değiştirmeyi istedi. Sizseniz, sıfırlamak için aşağıdaki linke tıklayınız.</p><p><a href=\"{0}\">Kimlik bilgilerini sıfırlamak için bağlantı</a></p><p>Bu bağlantının süresi {3} içerisinde sona erecek.</p><p>Kimlik bilgilerinizi sıfırlamak istemiyorsanız, bu mesajı göz ardı edin.</p>",
|
||||
"executeActionsSubject": "Hesabınızı Güncelleyin",
|
||||
"executeActionsBody": "Yöneticiniz aşağıdaki işlemleri gerçekleştirerek {2} hesabınızı güncelledi: {3}. Bu işlemi başlatmak için aşağıdaki linke tıklayın.\n\n{0}\n\nBu bağlantının süresi {4} içerisinde sona erecek.\n\nYöneticinizin bunu istediğinden habersizseniz, bu mesajı göz ardı edin ve hiçbir şey değişmez.",
|
||||
"executeActionsBodyHtml": "<p>Yöneticiniz aşağıdaki işlemleri gerçekleştirerek {2} hesabınızı güncelledi: {3}. Bu işlemi başlatmak için aşağıdaki linke tıklayın.</p><p><a href=\"{0}\">Hesap güncelleme bağlantısı</a></p><p>Bu bağlantının süresi {4} içerisinde sona erecek.</p><p>Yöneticinizin bunu istediğinden habersizseniz, bu mesajı göz ardı edin ve hiçbir şey değişmez.</p>",
|
||||
"eventLoginErrorSubject": "Giriş hatası",
|
||||
"eventLoginErrorBody": "{1} 'den {0} tarihinde başarısız bir giriş denemesi yapıldı. Bu siz değilseniz, lütfen yöneticiyle iletişime geçin.",
|
||||
"eventLoginErrorBodyHtml": "<p>{1} 'den {0} tarihinde başarısız bir giriş denemesi yapıldı. Bu siz değilseniz, lütfen yöneticiyle iletişime geçin.</p>",
|
||||
"eventRemoveTotpSubject": "OTP'yi kaldır",
|
||||
"eventRemoveTotpBody": "OTP, {0} tarihinden {1} tarihinde hesabınızdan kaldırıldı. Bu siz değilseniz, lütfen yöneticiyle iletişime geçin.",
|
||||
"eventRemoveTotpBodyHtml": "<p>OTP, {0} tarihinden {1} tarihinde hesabınızdan kaldırıldı. Bu siz değilseniz, lütfen yöneticiyle iletişime geçin.</p>",
|
||||
"eventUpdatePasswordSubject": "Şifreyi güncelle",
|
||||
"eventUpdatePasswordBody": "Şifreniz {0} tarihinde {0} tarihinde değiştirildi. Bu siz değilseniz, lütfen yöneticiyle iletişime geçin.",
|
||||
"eventUpdatePasswordBodyHtml": "<p>Şifreniz {0} tarihinde {0} tarihinde değiştirildi. Bu siz değilseniz, lütfen yöneticiyle iletişime geçin.</p>",
|
||||
"eventUpdateTotpSubject": "OTP'yi Güncelle",
|
||||
"eventUpdateTotpBody": "OTP, {0} tarihinden {1} tarihinde hesabınız için güncellendi. Bu siz değilseniz, lütfen yöneticiyle iletişime geçin.",
|
||||
"eventUpdateTotpBodyHtml": "<p>OTP, {0} tarihinden {1} tarihinde hesabınız için güncellendi. Bu siz değilseniz, lütfen yöneticiyle iletişime geçin.</p>",
|
||||
"requiredAction.CONFIGURE_TOTP": "OTP'yi yapılandır",
|
||||
"requiredAction.terms_and_conditions": "Şartlar ve Koşullar",
|
||||
"requiredAction.UPDATE_PASSWORD": "Şifre Güncelleme",
|
||||
"requiredAction.UPDATE_PROFILE": "Profilleri güncelle",
|
||||
"requiredAction.VERIFY_EMAIL": "E-mail doğrula",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds": "saniye",
|
||||
"linkExpirationFormatter.timePeriodUnit.seconds.1": "saniye",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes": "dakika",
|
||||
"linkExpirationFormatter.timePeriodUnit.minutes.1": "dakika",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours": "saat",
|
||||
"linkExpirationFormatter.timePeriodUnit.hours.1": "saat",
|
||||
"linkExpirationFormatter.timePeriodUnit.days": "gün",
|
||||
"linkExpirationFormatter.timePeriodUnit.days.1": "gün",
|
||||
"emailVerificationBodyCode": "Lütfen aşağıdaki kodu girerek e-posta adresinizi doğrulayın.\n\n{0}\n\n.",
|
||||
"emailVerificationBodyCodeHtml": "<p>Lütfen aşağıdaki kodu girerek e-posta adresinizi doğrulayın.</p><p><b>{0}</b></p>"
|
||||
},
|
||||
"zh-CN": {
|
||||
"emailVerificationSubject": "验证电子邮件",
|
||||
"emailVerificationBody": "用户使用当前电子邮件注册 {2} 账户。如是本人操作,请点击以下链接完成邮箱验证\n\n{0}\n\n这个链接会在 {1} 分钟后过期.\n\n如果您没有注册用户,请忽略这条消息。",
|
||||
"emailVerificationBodyHtml": "<p>用户使用当前电子邮件注册 {2} 账户。如是本人操作,请点击以下链接完成邮箱验证</p><p><a href=\"{0}\">{0}</a></p><p>这个链接会在 {1} 分钟后过期.</p><p>如果您没有注册用户,请忽略这条消息。</p>",
|
||||
"identityProviderLinkSubject": "链接 {0}",
|
||||
"identityProviderLinkBody": "有用户想要将账户 \"{1}\" 与用户{2}的账户\"{0}\" 做链接 . 如果是本人操作,请点击以下链接完成链接请求\n\n{3}\n\n这个链接会在 {4} 分钟后过期.\n\n如非本人操作,请忽略这条消息。如果您链接账户,您将可以通过{0}登录账户 {1}.",
|
||||
"identityProviderLinkBodyHtml": "<p>有用户想要将账户 <b>{1}</b> 与用户{2} 的账户<b>{0}</b> 做链接 . 如果是本人操作,请点击以下链接完成链接请求</p><p><a href=\"{3}\">{3}</a></p><p>这个链接会在 {4} 分钟后过期。</p><p>如非本人操作,请忽略这条消息。如果您链接账户,您将可以通过{0}登录账户 {1}.</p>",
|
||||
"passwordResetSubject": "重置密码",
|
||||
"passwordResetBody": "有用户要求修改账户 {2} 的密码.如是本人操作,请点击下面链接进行重置.\n\n{0}\n\n这个链接会在 {1} 分钟后过期.\n\n如果您不想重置您的密码,请忽略这条消息,密码不会改变。",
|
||||
"passwordResetBodyHtml": "<p>有用户要求修改账户 {2} 的密码如是本人操作,请点击下面链接进行重置.</p><p><a href=\"{0}\">{0}</a></p><p>这个链接会在 {1} 分钟后过期</p><p>如果您不想重置您的密码,请忽略这条消息,密码不会改变。</p>",
|
||||
"executeActionsSubject": "更新您的账户",
|
||||
"executeActionsBody": "您的管理员要求您更新账户 {2}. 点击以下链接开始更新\n\n{0}\n\n这个链接会在 {1} 分钟后失效.\n\n如果您不知道管理员要求更新账户信息,请忽略这条消息。账户信息不会修改。",
|
||||
"executeActionsBodyHtml": "<p>您的管理员要求您更新账户{2}. 点击以下链接开始更新.</p><p><a href=\"{0}\">{0}</a></p><p>这个链接会在 {1} 分钟后失效.</p><p>如果您不知道管理员要求更新账户信息,请忽略这条消息。账户信息不会修改。</p>",
|
||||
"eventLoginErrorSubject": "登录错误",
|
||||
"eventLoginErrorBody": "在{0} 由 {1}使用您的账户登录失败. 如果这不是您本人操作,请联系管理员.",
|
||||
"eventLoginErrorBodyHtml": "<p>在{0} 由 {1}使用您的账户登录失败. 如果这不是您本人操作,请联系管理员.</p>",
|
||||
"eventRemoveTotpSubject": "删除 OTP",
|
||||
"eventRemoveTotpBody": "OTP在 {0} 由{1} 从您的账户中删除.如果这不是您本人操作,请联系管理员",
|
||||
"eventRemoveTotpBodyHtml": "<p>OTP在 {0} 由{1} 从您的账户中删除.如果这不是您本人操作,请联系管理员。</p>",
|
||||
"eventUpdatePasswordSubject": "更新密码",
|
||||
"eventUpdatePasswordBody": "您的密码在{0} 由 {1}更改. 如非本人操作,请联系管理员",
|
||||
"eventUpdatePasswordBodyHtml": "<p>您的密码在{0} 由 {1}更改. 如非本人操作,请联系管理员</p>",
|
||||
"eventUpdateTotpSubject": "更新 OTP",
|
||||
"eventUpdateTotpBody": "您账户的OTP 配置在{0} 由 {1}更改. 如非本人操作,请联系管理员。",
|
||||
"eventUpdateTotpBodyHtml": "<p>您账户的OTP 配置在{0} 由 {1}更改. 如非本人操作,请联系管理员。</p>"
|
||||
}
|
||||
} as const;
|
||||
/* spell-checker: enable */
|
4364
src/lib/i18n/generated_messages/login.ts
Normal file
4364
src/lib/i18n/generated_messages/login.ts
Normal file
File diff suppressed because it is too large
Load Diff
23
src/lib/i18n/useKcLanguageTag.ts
Normal file
23
src/lib/i18n/useKcLanguageTag.ts
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
import { createUseGlobalState } from "powerhooks";
|
||||
import { kcContext } from "../KcContext";
|
||||
import { getBestMatchAmongKcLanguageTag } from "./KcLanguageTag";
|
||||
|
||||
//export const { useKcLanguageTag, evtKcLanguageTag } = createUseGlobalState(
|
||||
const wrap = createUseGlobalState(
|
||||
"kcLanguageTag",
|
||||
() => getBestMatchAmongKcLanguageTag(
|
||||
kcContext?.locale?.current ??
|
||||
navigator.language
|
||||
),
|
||||
{ "persistance": "cookie" }
|
||||
);
|
||||
|
||||
export const { useKcLanguageTag } = wrap;
|
||||
|
||||
export function getEvtKcLanguage() {
|
||||
return wrap.evtKcLanguageTag;
|
||||
}
|
||||
|
||||
|
||||
|
43
src/lib/i18n/useKcMessage.tsx
Normal file
43
src/lib/i18n/useKcMessage.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
|
||||
import { useKcLanguageTag } from "./useKcLanguageTag";
|
||||
import { messages } from "./generated_messages/login";
|
||||
import { useConstCallback } from "powerhooks";
|
||||
import type { ReactNode } from "react";
|
||||
import { id } from "evt/tools/typeSafety/id";
|
||||
|
||||
export type MessageKey = keyof typeof messages["en"];
|
||||
|
||||
export function useKcMessage() {
|
||||
|
||||
const { kcLanguageTag } = useKcLanguageTag();
|
||||
|
||||
const msgStr = useConstCallback(
|
||||
(key: MessageKey, ...args: (string | undefined)[]): string => {
|
||||
|
||||
let str: string = messages[kcLanguageTag as any as "en"][key] ?? messages["en"][key];
|
||||
|
||||
args.forEach((arg, i) => {
|
||||
|
||||
if (arg === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
str = str.replace(new RegExp(`\\{${i}\\}`, "g"), arg);
|
||||
|
||||
});
|
||||
|
||||
return str;
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
const msg = useConstCallback(
|
||||
id<(...args: Parameters<typeof msgStr>) => ReactNode>(
|
||||
(key, ...args) =>
|
||||
<span className={key} dangerouslySetInnerHTML={{ "__html": msgStr(key, ...args) }} />
|
||||
)
|
||||
);
|
||||
|
||||
return { msg, msgStr };
|
||||
|
||||
}
|
19
src/lib/index.ts
Normal file
19
src/lib/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export * from "./KcContext";
|
||||
|
||||
export * from "./i18n/KcLanguageTag";
|
||||
export * from "./i18n/useKcLanguageTag";
|
||||
export * from "./i18n/useKcMessage";
|
||||
|
||||
export * from "./components/KcProps";
|
||||
export * from "./components/Login";
|
||||
export * from "./components/Template";
|
||||
export * from "./components/KcApp";
|
||||
export * from "./components/Info";
|
||||
export * from "./components/Error";
|
||||
export * from "./components/LoginResetPassword";
|
||||
export * from "./components/LoginVerifyEmail";
|
||||
export * from "./keycloakJsAdapter";
|
||||
|
||||
export * from "./tools/assert";
|
||||
|
||||
export * as kcContextMocks from "./kcContextMocks";
|
@ -1,3 +0,0 @@
|
||||
|
||||
|
||||
export { };
|
160
src/lib/kcContext.ts
Normal file
160
src/lib/kcContext.ts
Normal file
@ -0,0 +1,160 @@
|
||||
|
||||
import { ftlValuesGlobalName } from "../bin/build-keycloak-theme/ftlValuesGlobalName";
|
||||
import type { PageId } from "../bin/build-keycloak-theme/generateFtl";
|
||||
import { id } from "evt/tools/typeSafety/id";
|
||||
import type { KcLanguageTag } from "./i18n/KcLanguageTag";
|
||||
import { doExtends } from "evt/tools/typeSafety/doExtends";
|
||||
import type { MessageKey } from "./i18n/useKcMessage";
|
||||
import type { LanguageLabel } from "./i18n/KcLanguageTag";
|
||||
|
||||
type ExtractAfterStartingWith<Prefix extends string, StrEnum> =
|
||||
StrEnum extends `${Prefix}${infer U}` ? U : never;
|
||||
|
||||
export type KcContext =
|
||||
KcContext.Login | KcContext.Register | KcContext.Info |
|
||||
KcContext.Error | KcContext.LoginResetPassword | KcContext.LoginVerifyEmail;
|
||||
export declare namespace KcContext {
|
||||
|
||||
export type Template = {
|
||||
url: {
|
||||
loginAction: string;
|
||||
resourcesPath: string;
|
||||
resourcesCommonPath: string;
|
||||
loginRestartFlowUrl: string;
|
||||
loginUrl: string;
|
||||
};
|
||||
realm: {
|
||||
displayName?: string;
|
||||
displayNameHtml?: string;
|
||||
internationalizationEnabled: boolean;
|
||||
registrationEmailAsUsername: boolean; //<---
|
||||
};
|
||||
/** Undefined if !realm.internationalizationEnabled */
|
||||
locale?: {
|
||||
supported: {
|
||||
url: string;
|
||||
languageTag: KcLanguageTag;
|
||||
/** Is determined by languageTag. Ex: languageTag === "en" => label === "English"
|
||||
* or getLanguageLabel(languageTag) === label
|
||||
*/
|
||||
//label: LanguageLabel;
|
||||
}[];
|
||||
current: LanguageLabel;
|
||||
},
|
||||
auth?: {
|
||||
showUsername: boolean;
|
||||
showResetCredentials: boolean;
|
||||
showTryAnotherWayLink: boolean;
|
||||
attemptedUsername?: string;
|
||||
};
|
||||
scripts: string[];
|
||||
message?: {
|
||||
type: "success" | "warning" | "error" | "info";
|
||||
summary: string;
|
||||
};
|
||||
isAppInitiatedAction: boolean;
|
||||
};
|
||||
|
||||
export type Login = Template & {
|
||||
pageId: "login.ftl";
|
||||
url: {
|
||||
loginResetCredentialsUrl: string;
|
||||
registrationUrl: string;
|
||||
};
|
||||
realm: {
|
||||
loginWithEmailAllowed: boolean;
|
||||
rememberMe: boolean;
|
||||
password: boolean;
|
||||
resetPasswordAllowed: boolean;
|
||||
registrationAllowed: boolean;
|
||||
};
|
||||
auth: {
|
||||
selectedCredential?: string;
|
||||
};
|
||||
registrationDisabled: boolean;
|
||||
login: {
|
||||
username?: string;
|
||||
rememberMe: boolean;
|
||||
};
|
||||
usernameEditDisabled: boolean;
|
||||
social: {
|
||||
displayInfo: boolean;
|
||||
providers?: {
|
||||
loginUrl: string;
|
||||
alias: string;
|
||||
providerId: string;
|
||||
displayName: string;
|
||||
}[]
|
||||
};
|
||||
};
|
||||
|
||||
export type Register = Template & {
|
||||
pageId: "register.ftl";
|
||||
url: {
|
||||
registrationAction: string;
|
||||
};
|
||||
messagesPerField: {
|
||||
printIfExists<T>(
|
||||
key:
|
||||
"userLabel" |
|
||||
"username" |
|
||||
"email" |
|
||||
"firstName" |
|
||||
"lastName" |
|
||||
"password" |
|
||||
"password-confirm",
|
||||
x: T
|
||||
): T | undefined;
|
||||
};
|
||||
register: {
|
||||
formData: {
|
||||
firstName?: string;
|
||||
displayName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
username?: string;
|
||||
}
|
||||
};
|
||||
passwordRequired: boolean;
|
||||
recaptchaRequired: boolean;
|
||||
recaptchaSiteKey?: string;
|
||||
};
|
||||
|
||||
export type Info = Template & {
|
||||
pageId: "info.ftl";
|
||||
messageHeader?: string;
|
||||
requiredActions?: ExtractAfterStartingWith<"requiredAction.", MessageKey>[];
|
||||
skipLink: boolean;
|
||||
pageRedirectUri?: string;
|
||||
actionUri?: string;
|
||||
client: {
|
||||
baseUrl?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type Error = Template & {
|
||||
pageId: "error.ftl";
|
||||
client?: {
|
||||
baseUrl?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type LoginResetPassword = Template & {
|
||||
pageId: "login-reset-password.ftl";
|
||||
realm: {
|
||||
loginWithEmailAllowed: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export type LoginVerifyEmail = Template & {
|
||||
pageId: "login-verify-email.ftl";
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
doExtends<KcContext["pageId"], PageId>();
|
||||
doExtends<PageId, KcContext["pageId"]>();
|
||||
|
||||
export const kcContext = id<KcContext | undefined>((window as any)[ftlValuesGlobalName]);
|
||||
|
||||
|
201
src/lib/kcContextMocks/index.ts
Normal file
201
src/lib/kcContextMocks/index.ts
Normal file
@ -0,0 +1,201 @@
|
||||
|
||||
|
||||
import type { KcContext } from "../KcContext";
|
||||
import { getEvtKcLanguage } from "../i18n/useKcLanguageTag";
|
||||
import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
|
||||
//NOTE: Aside because we want to be able to import them from node
|
||||
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
|
||||
|
||||
export const kcTemplateContext: KcContext.Template = {
|
||||
"url": {
|
||||
"loginAction": "#",
|
||||
"resourcesPath": "/" + resourcesPath,
|
||||
"resourcesCommonPath": "/" + resourcesCommonPath,
|
||||
"loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
|
||||
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
|
||||
},
|
||||
"realm": {
|
||||
"displayName": "myrealm",
|
||||
"displayNameHtml": "myrealm",
|
||||
"internationalizationEnabled": true,
|
||||
"registrationEmailAsUsername": true,
|
||||
},
|
||||
"locale": {
|
||||
"supported": [
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
|
||||
"languageTag": "de"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
|
||||
"languageTag": "no"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
||||
"languageTag": "ru"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
|
||||
"languageTag": "sv"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
|
||||
"languageTag": "pt-BR"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
|
||||
"languageTag": "lt"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
|
||||
"languageTag": "en"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
|
||||
"languageTag": "it"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
|
||||
"languageTag": "fr"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
|
||||
"languageTag": "zh-CN"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
|
||||
"languageTag": "es"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
|
||||
"languageTag": "cs"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
||||
"languageTag": "ja"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
|
||||
"languageTag": "sk"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
|
||||
"languageTag": "pl"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
|
||||
"languageTag": "ca"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
|
||||
"languageTag": "nl"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
|
||||
"languageTag": "tr"
|
||||
}
|
||||
],
|
||||
"current": null as any
|
||||
},
|
||||
"auth": {
|
||||
"showUsername": false,
|
||||
"showResetCredentials": false,
|
||||
"showTryAnotherWayLink": false
|
||||
},
|
||||
"scripts": [],
|
||||
"message": {
|
||||
"type": "success",
|
||||
"summary": "This is a test message"
|
||||
},
|
||||
"isAppInitiatedAction": false,
|
||||
};
|
||||
|
||||
Object.defineProperty(
|
||||
kcTemplateContext.locale!,
|
||||
"current",
|
||||
{
|
||||
"get": () => getKcLanguageTagLabel(getEvtKcLanguage().state),
|
||||
"enumerable": true
|
||||
}
|
||||
);
|
||||
|
||||
export const kcLoginContext: KcContext.Login = {
|
||||
...kcTemplateContext,
|
||||
"pageId": "login.ftl",
|
||||
"url": {
|
||||
...kcTemplateContext.url,
|
||||
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
|
||||
"registrationUrl": "/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg"
|
||||
},
|
||||
"realm": {
|
||||
...kcTemplateContext.realm,
|
||||
"loginWithEmailAllowed": true,
|
||||
"rememberMe": true,
|
||||
"password": true,
|
||||
"resetPasswordAllowed": true,
|
||||
"registrationAllowed": true
|
||||
},
|
||||
"auth": kcTemplateContext.auth!,
|
||||
"social": {
|
||||
"displayInfo": true
|
||||
},
|
||||
"usernameEditDisabled": false,
|
||||
"login": {
|
||||
"rememberMe": false
|
||||
},
|
||||
"registrationDisabled": false,
|
||||
};
|
||||
|
||||
export const kcRegisterContext: KcContext.Register = {
|
||||
...kcTemplateContext,
|
||||
"url": {
|
||||
...kcLoginContext.url,
|
||||
"registrationAction": "http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0"
|
||||
},
|
||||
"messagesPerField": {
|
||||
"printIfExists": (...[,x]) => x
|
||||
},
|
||||
"scripts": [],
|
||||
"isAppInitiatedAction": false,
|
||||
"pageId": "register.ftl",
|
||||
"register": {
|
||||
"formData": {}
|
||||
},
|
||||
"passwordRequired": true,
|
||||
"recaptchaRequired": false
|
||||
};
|
||||
|
||||
export const kcInfoContext: KcContext.Info ={
|
||||
...kcTemplateContext,
|
||||
"pageId": "info.ftl",
|
||||
"messageHeader": "<Message header>",
|
||||
"requiredActions": undefined,
|
||||
"skipLink": false,
|
||||
"actionUri": "#",
|
||||
"client": {
|
||||
"baseUrl": "#"
|
||||
}
|
||||
};
|
||||
|
||||
export const kcErrorContext: KcContext.Error = {
|
||||
...kcTemplateContext,
|
||||
"pageId": "error.ftl",
|
||||
"client": {
|
||||
"baseUrl": "#"
|
||||
}
|
||||
};
|
||||
|
||||
export const kcLoginResetPasswordContext: KcContext.LoginResetPassword = {
|
||||
...kcTemplateContext,
|
||||
"pageId": "login-reset-password.ftl",
|
||||
"realm":{
|
||||
...kcTemplateContext.realm,
|
||||
"loginWithEmailAllowed": false
|
||||
}
|
||||
};
|
||||
|
||||
export const kcLoginVerifyEmailContext: KcContext.LoginVerifyEmail = {
|
||||
...kcTemplateContext,
|
||||
"pageId": "login-verify-email.ftl"
|
||||
};
|
||||
|
6
src/lib/kcContextMocks/urlResourcesPath.ts
Normal file
6
src/lib/kcContextMocks/urlResourcesPath.ts
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
import { join as pathJoin } from "path";
|
||||
|
||||
export const subDirOfPublicDirBasename = "keycloak_static";
|
||||
export const resourcesPath = pathJoin(subDirOfPublicDirBasename, "/resources");
|
||||
export const resourcesCommonPath = pathJoin(subDirOfPublicDirBasename, "/resources_common");
|
119
src/lib/keycloakJsAdapter.ts
Normal file
119
src/lib/keycloakJsAdapter.ts
Normal file
@ -0,0 +1,119 @@
|
||||
|
||||
|
||||
export declare namespace keycloak_js {
|
||||
|
||||
export type KeycloakPromiseCallback<T> = (result: T) => void;
|
||||
export class KeycloakPromise<TSuccess, TError> extends Promise<TSuccess> {
|
||||
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
|
||||
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
|
||||
}
|
||||
export interface KeycloakAdapter {
|
||||
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
logout(options?: KeycloakLogoutOptions): KeycloakPromise<void, void>;
|
||||
register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
accountManagement(): KeycloakPromise<void, void>;
|
||||
redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string;
|
||||
}
|
||||
export interface KeycloakLogoutOptions {
|
||||
redirectUri?: string;
|
||||
}
|
||||
export interface KeycloakLoginOptions {
|
||||
scope?: string;
|
||||
redirectUri?: string;
|
||||
prompt?: 'none' | 'login';
|
||||
action?: string;
|
||||
maxAge?: number;
|
||||
loginHint?: string;
|
||||
idpHint?: string;
|
||||
locale?: string;
|
||||
cordovaOptions?: { [optionName: string]: string };
|
||||
}
|
||||
|
||||
export type KeycloakInstance = Record<
|
||||
"createLoginUrl" |
|
||||
"createLogoutUrl" |
|
||||
"createRegisterUrl",
|
||||
(options: KeycloakLoginOptions | undefined) => string
|
||||
> & {
|
||||
createAccountUrl(): string;
|
||||
redirectUri?: string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This is just a slightly modified version of the default adapter in keycloak-js
|
||||
* The goal here is just to be able to inject search param in url before keycloak redirect.
|
||||
* Our use case for it is to pass over the login screen the states of useGlobalState
|
||||
* namely isDarkModeEnabled, lgn...
|
||||
*/
|
||||
export function createKeycloakAdapter(
|
||||
params: {
|
||||
keycloakInstance: keycloak_js.KeycloakInstance;
|
||||
transformUrlBeforeRedirect(url: string): string;
|
||||
}
|
||||
): keycloak_js.KeycloakAdapter {
|
||||
|
||||
const { keycloakInstance, transformUrlBeforeRedirect } = params;
|
||||
|
||||
const neverResolvingPromise: keycloak_js.KeycloakPromise<void, void> = Object.defineProperties(
|
||||
new Promise(() => { }),
|
||||
{
|
||||
"success": { "value": () => { } },
|
||||
"error": { "value": () => { } }
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
"login": options => {
|
||||
window.location.replace(
|
||||
transformUrlBeforeRedirect(
|
||||
keycloakInstance.createLoginUrl(
|
||||
options
|
||||
)
|
||||
)
|
||||
);
|
||||
return neverResolvingPromise;
|
||||
},
|
||||
"logout": options => {
|
||||
window.location.replace(
|
||||
transformUrlBeforeRedirect(
|
||||
keycloakInstance.createLogoutUrl(
|
||||
options
|
||||
)
|
||||
)
|
||||
);
|
||||
return neverResolvingPromise;
|
||||
},
|
||||
"register": options => {
|
||||
window.location.replace(
|
||||
transformUrlBeforeRedirect(
|
||||
keycloakInstance.createRegisterUrl(
|
||||
options
|
||||
)
|
||||
)
|
||||
);
|
||||
return neverResolvingPromise;
|
||||
},
|
||||
"accountManagement": () => {
|
||||
var accountUrl = transformUrlBeforeRedirect(keycloakInstance.createAccountUrl());
|
||||
if (typeof accountUrl !== 'undefined') {
|
||||
window.location.href = accountUrl;
|
||||
} else {
|
||||
throw new Error("Not supported by the OIDC server");
|
||||
}
|
||||
return neverResolvingPromise;
|
||||
},
|
||||
"redirectUri": options => {
|
||||
if (options && options.redirectUri) {
|
||||
return options.redirectUri;
|
||||
} else if (keycloakInstance.redirectUri) {
|
||||
return keycloakInstance.redirectUri;
|
||||
} else {
|
||||
return window.location.href;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
10
src/lib/tools/allPropertiesValuesToUndefined.ts
Normal file
10
src/lib/tools/allPropertiesValuesToUndefined.ts
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
|
||||
import "minimal-polyfills/Object.fromEntries";
|
||||
|
||||
export function allPropertiesValuesToUndefined<T extends Record<string, unknown>>(obj: T): Record<keyof T, undefined> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj)
|
||||
.map(([key]) => [key, undefined])
|
||||
) as any;
|
||||
}
|
49
src/lib/tools/appendHead.ts
Normal file
49
src/lib/tools/appendHead.ts
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
import { Deferred } from "evt/tools/Deferred";
|
||||
|
||||
export function appendHead(
|
||||
params: {
|
||||
type: "css";
|
||||
href: string;
|
||||
} | {
|
||||
type: "javascript";
|
||||
src: string;
|
||||
}
|
||||
) {
|
||||
|
||||
const htmlElement = document.createElement(
|
||||
(() => {
|
||||
switch (params.type) {
|
||||
case "css": return "link";
|
||||
case "javascript": return "script";
|
||||
}
|
||||
})()
|
||||
);
|
||||
|
||||
const dLoaded = new Deferred<void>();
|
||||
|
||||
htmlElement.addEventListener("load", () => dLoaded.resolve());
|
||||
|
||||
Object.assign(
|
||||
htmlElement,
|
||||
(() => {
|
||||
switch (params.type) {
|
||||
case "css": return {
|
||||
"href": params.href,
|
||||
"type": "text/css",
|
||||
"rel": "stylesheet",
|
||||
"media": "screen,print"
|
||||
};
|
||||
case "javascript": return {
|
||||
"src": params.src,
|
||||
"type": "text/javascript",
|
||||
};
|
||||
}
|
||||
})()
|
||||
);
|
||||
|
||||
document.getElementsByTagName("head")[0].appendChild(htmlElement);
|
||||
|
||||
return dLoaded.pr;
|
||||
|
||||
}
|
2
src/lib/tools/assert.ts
Normal file
2
src/lib/tools/assert.ts
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
export { assert } from "evt/tools/typeSafety/assert";
|
@ -1,23 +0,0 @@
|
||||
|
||||
import {
|
||||
setupSampleReactProject,
|
||||
sampleReactProjectDirPath
|
||||
} from "./setupSampleReactProject";
|
||||
import * as st from "scripting-tools";
|
||||
import { join as pathJoin } from "path";
|
||||
|
||||
|
||||
setupSampleReactProject();
|
||||
|
||||
|
||||
console.log(`Running main in ${sampleReactProjectDirPath}`);
|
||||
|
||||
console.log(
|
||||
st.execSync(
|
||||
`node ${pathJoin(__dirname, "../bin/build-keycloak-theme")}`,
|
||||
{ "cwd": sampleReactProjectDirPath }
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
|
||||
import { sampleReactProjectDirPath } from "./setupSampleReactProject";
|
||||
import * as st from "scripting-tools";
|
||||
import { join as pathJoin } from "path";
|
||||
|
||||
console.log(`Running main in ${sampleReactProjectDirPath}`);
|
||||
|
||||
st.execSync(
|
||||
`node ${pathJoin(__dirname, "../bin/download-sample-keycloak-themes")}`,
|
||||
{ "cwd": sampleReactProjectDirPath }
|
||||
);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
import { join as pathJoin } from "path";
|
||||
import { generateKeycloakThemeResources } from "../bin/build-keycloak-theme/generateKeycloakThemeResources";
|
||||
import {
|
||||
import {
|
||||
setupSampleReactProject,
|
||||
sampleReactProjectDirPath
|
||||
} from "./setupSampleReactProject";
|
||||
@ -9,8 +9,12 @@ import {
|
||||
setupSampleReactProject();
|
||||
|
||||
generateKeycloakThemeResources({
|
||||
"themeName": "onyxia-ui",
|
||||
"themeName": "keycloakify-demo-app",
|
||||
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
|
||||
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme")
|
||||
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"),
|
||||
"mode": {
|
||||
"type": "standalone",
|
||||
"urlPathname": "/keycloakify-demo-app/"
|
||||
}
|
||||
});
|
||||
|
||||
|
24
src/test/index.ts
Normal file
24
src/test/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
|
||||
import {
|
||||
setupSampleReactProject,
|
||||
sampleReactProjectDirPath
|
||||
} from "./setupSampleReactProject";
|
||||
import * as st from "scripting-tools";
|
||||
import { join as pathJoin } from "path";
|
||||
import { getProjectRoot } from "../bin/tools/getProjectRoot";
|
||||
|
||||
setupSampleReactProject();
|
||||
|
||||
const binDirPath= pathJoin(getProjectRoot(), "dist", "bin");
|
||||
|
||||
st.execSyncTrace(
|
||||
`node ${pathJoin(binDirPath, "build-keycloak-theme")}`,
|
||||
{ "cwd": sampleReactProjectDirPath }
|
||||
);
|
||||
|
||||
st.execSyncTrace(
|
||||
`node ${pathJoin(binDirPath, "install-builtin-keycloak-themes")}`,
|
||||
{ "cwd": sampleReactProjectDirPath }
|
||||
);
|
||||
|
@ -1,11 +1,11 @@
|
||||
|
||||
import {
|
||||
replaceImportFromStaticInJsCode,
|
||||
replaceImportFromStaticInCssCode,
|
||||
replaceImportsFromStaticInJsCode,
|
||||
replaceImportsInCssCode,
|
||||
generateCssCodeToDefineGlobals
|
||||
} from "../bin/build-keycloak-theme/replaceImportFromStatic";
|
||||
|
||||
const { fixedJsCode } = replaceImportFromStaticInJsCode({
|
||||
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
||||
"ftlValuesGlobalName": "keycloakFtlValues",
|
||||
"jsCode": `
|
||||
function f() {
|
||||
@ -19,12 +19,13 @@ const { fixedJsCode } = replaceImportFromStaticInJsCode({
|
||||
3: "0664cdc0"
|
||||
}[e] + ".chunk.js"
|
||||
}
|
||||
`
|
||||
`,
|
||||
"mode": { "type": "standalone" }
|
||||
});
|
||||
|
||||
console.log({ fixedJsCode });
|
||||
|
||||
const { fixedCssCode, cssGlobalsToDefine } = replaceImportFromStaticInCssCode({
|
||||
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
|
||||
"cssCode": `
|
||||
|
||||
.my-div {
|
||||
@ -45,6 +46,6 @@ const { fixedCssCode, cssGlobalsToDefine } = replaceImportFromStaticInCssCode({
|
||||
console.log({ fixedCssCode, cssGlobalsToDefine });
|
||||
|
||||
|
||||
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({ cssGlobalsToDefine });
|
||||
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({ cssGlobalsToDefine, "urlPathname": "/" });
|
||||
|
||||
console.log({ cssCodeToPrependInHead });
|
@ -1,19 +1,14 @@
|
||||
|
||||
import * as st from "scripting-tools";
|
||||
import { join as pathJoin, basename as pathBasename } from "path";
|
||||
import { getProjectRoot } from "../bin/tools/getProjectRoot";
|
||||
import { join as pathJoin } from "path";
|
||||
import { downloadAndUnzip } from "../bin/tools/downloadAndUnzip";
|
||||
|
||||
export const sampleReactProjectDirPath = pathJoin(__dirname, "..", "..", "sample_react_project");
|
||||
export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project");
|
||||
|
||||
export function setupSampleReactProject() {
|
||||
|
||||
st.execSync(`rm -rf ${sampleReactProjectDirPath}`);
|
||||
st.execSync(`mkdir ${sampleReactProjectDirPath}`);
|
||||
|
||||
const url = "https://github.com/garronej/keycloak-react-theming/releases/download/v0.0.1/sample_build_dir_and_package_json.zip";
|
||||
|
||||
[
|
||||
`wget ${url}`,
|
||||
...["unzip", "rm"].map(prg => `${prg} ${pathBasename(url)}`)
|
||||
].forEach(cmd => st.execSync(cmd, { "cwd": sampleReactProjectDirPath }));
|
||||
|
||||
}
|
||||
downloadAndUnzip({
|
||||
"url": "https://github.com/garronej/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
|
||||
"destDirPath": sampleReactProjectDirPath
|
||||
});
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"target": "es5",
|
||||
"lib": ["es2015", "DOM"],
|
||||
"lib": ["es2015", "DOM", "ES2019.Object"],
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
|
Reference in New Issue
Block a user