Add page localization (#7128)

* added localized pages; added refactored page templates; adapted test cases; introduced localization test cases

* added changelog entry

* fixed test description typo

* fixed bug in PromiseRouter where headers are not added for text reponse

* added page parameters in page headers for programmatic use

* refactored tests for PublicAPIRouter

* added mustache lib for template rendering

* fixed fs.promises module reference

* fixed template placeholder typo

* changed redirect response to provide headers instead of query parameters

* fix lint

* fixed syntax errors and typos in html templates

* removed obsolete URI encoding

* added locale inferring from request body and header

* added end-to-end localizaton test

* added server option validation; refactored pages server option

* fixed invalid redirect URL for no locale matching file

* added end-to-end localizaton tests

* adapted tests to new response content

* re-added PublicAPIRouter; added PagesRouter as experimental feature

* refactored PagesRouter test structure

* added configuration option for custom path to pages

* added configuration option for custom endpoint to pages

* fixed lint

* added tests

* added a distinct page for invalid password reset link

* renamed generic page invalidLink to expiredVerificationLink

* improved HTML files documentation

* improved HTML files documentation

* changed changelog entry for experimental feature

* improved file naming to make it more descriptive

* fixed file naming and env parameter naming

* added readme entry

* fixed readme TOC - hasn't been updated in a while

* added localization with JSON resource

* added JSON localization to feature pages (password reset, email verification)

* updated readme

* updated readme

* optimized JSON localization for feature pages; added e2e test case

* fixed readme typo

* minor refactoring of existing tests

* fixed bug where Object type was not recognized as config key type

* added feature config placeholders

* prettier

* added passing locale to page config placeholder callback

* refactored passing locale to placeholder to pass test

* added config placeholder feature to README

* fixed typo in README
This commit is contained in:
Manuel
2021-02-09 14:03:57 +01:00
committed by GitHub
parent e3ed6e4600
commit 7f47b0427e
41 changed files with 4335 additions and 2903 deletions

17
public/custom_json.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<!--
This page demonstrates how to localize using a JSON file that contains the
translations for each placeholder.
-->
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<h1>{{heading}}</h1>
<p>{{body}}</p>
</body>
</html>

23
public/custom_json.json Normal file
View File

@@ -0,0 +1,23 @@
{
"en": {
"translation": {
"title": "Hello!",
"heading": "Welcome to {{appName}}!",
"body": "We are delighted to welcome you on board."
}
},
"de": {
"translation": {
"title": "Hallo!",
"heading": "Willkommen bei {{appName}}!",
"body": "Wir freuen uns, dich begrüßen zu dürfen."
}
},
"de-AT": {
"translation": {
"title": "Servus!",
"heading": "Willkommen bei {{appName}}!",
"body": "Wir freuen uns, dich begrüßen zu dürfen."
}
}
}

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with a security
token that is expired or incorrect. This can either mean the user has clicked
on a stale link (i.e. re-clicked on the link) or this could be a sign of a
malicious user trying to tamper with your app.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Expired verification link!</h1>
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
<input name="username" type="hidden" value="{{{username}}}">
<input name="locale" type="hidden" value="{{{locale}}}">
<button type="submit">Resend Link</button>
</form>
</body>
</html>

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with parameters
that are missing or incorrect. This can either mean the user has incorrectly
entered a link or this could be a sign of a malicious user trying to tamper
with your app.
If the link contains an expired security token (or the email has already
been verified), this page is not displayed, there is another page for that.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Invalid verification link!</h1>
</body>
</html>

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with a security
token that is expired or incorrect, then requests to receive another link,
but it fails because the username is invalid or the email has already been
verified. This can either mean the user has previously verified the email
or this could be a sign of a malicious user trying to tamper with your app.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Invalid link!</h1>
<p>No link sent. User not found or email already verified.</p>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with a
security token that is expired, then requests to receive another email
with a new link and the email is sent successfully.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Link sent!</h1>
<p>A new link has been sent. Check your email.</p>
</body>
</html>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link and the
email gets verified successfully.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Email verified!</h1>
<p>Successfully verified your email for account: {{username}}.</p>
</body>
</html>

View File

@@ -0,0 +1,65 @@
<!DOCTYPE html>
<!--
This page is displayed when someone clicks a valid 'reset password' link.
Users should feel free to add to this page (i.e. branding or security widgets)
but should be sure not to delete any of the form inputs or the javascript from the
template file. This javascript is what adds the necessary values to authenticate
this session with Parse.
The query params 'username' and 'app' hold the friendly names for your current user and
your app. You should feel free to incorporate their values to make the page more personal.
If you are missing form parameters in your POST, Parse will navigate back to this page and
add an 'error' query parameter.
-->
<html>
<head>
<title>Password Reset</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Reset Your Password</h1>
<noscript><p>We apologize, but resetting your password requires javascript</p></noscript>
<p>You can set a new Password for your account: {{username}}</p>
<br />
<p>{{error}}</p>
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
<input name='utf-8' type='hidden' value='✓' />
<input name="username" type="hidden" id="username" value="{{{username}}}" />
<input name="token" type="hidden" id="token" value="{{{token}}}" />
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
<p>New Password</p>
<input name="new_password" type="password" id="password" />
<p>Confirm New Password</p>
<input name="confirm_new_password" type="password" id="password_confirm" />
<br />
<p id="password_match_info"></p>
<br />
<button id="change_password">Change Password</button>
</form>
<script>
window.onload = function() {
document.getElementById("password").oninput = validatePassword;
document.getElementById("password_confirm").oninput = validatePassword;
document.getElementById("change_password").disabled = true;
function validatePassword() {
var pass2 = document.getElementById("password").value;
var pass1 = document.getElementById("password_confirm").value;
if(pass1 !== pass2) {
if(document.getElementById("password_confirm").value) {
document.getElementById("change_password").disabled = true;
document.getElementById("password_match_info").innerHTML = "Must match the previous entry";
}
} else {
document.getElementById("change_password").disabled = false;
document.getElementById("password_match_info").innerHTML = "";
}
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a password reset link with parameters
that are missing or incorrect. This can either mean the user has incorrectly
entered a link or this could be a sign of a malicious user trying to tamper
with your app.
-->
<html>
<head>
<title>Password Reset</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Invalid password reset link!</h1>
</body>
</html>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a reset password link, then submits
the form with a new password and the password gets updated successfully.
-->
<html>
<head>
<title>Password Reset</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Success!</h1>
<p>Your password has been updated.</p>
</body>
</html>

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with a security
token that is expired or incorrect. This can either mean the user has clicked
on a stale link (i.e. re-clicked on the link) or this could be a sign of a
malicious user trying to tamper with your app.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Expired verification link!</h1>
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
<input name="username" type="hidden" value="{{{username}}}">
<input name="locale" type="hidden" value="{{{locale}}}">
<button type="submit">Resend Link</button>
</form>
</body>
</html>

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with parameters
that are missing or incorrect. This can either mean the user has incorrectly
entered a link or this could be a sign of a malicious user trying to tamper
with your app.
If the link contains an expired security token (or the email has already
been verified), this page is not displayed, there is another page for that.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Invalid verification link!</h1>
</body>
</html>

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with a security
token that is expired or incorrect, then requests to receive another link,
but it fails because the username is invalid or the email has already been
verified. This can either mean the user has previously verified the email
or this could be a sign of a malicious user trying to tamper with your app.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Invalid link!</h1>
<p>No link sent. User not found or email already verified.</p>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with a
security token that is expired, then requests to receive another email
with a new link and the email is sent successfully.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Link sent!</h1>
<p>A new link has been sent. Check your email.</p>
</body>
</html>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link and the
email gets verified successfully.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Email verified!</h1>
<p>Successfully verified your email for account: {{username}}.</p>
</body>
</html>

View File

@@ -0,0 +1,65 @@
<!DOCTYPE html>
<!--
This page is displayed when someone clicks a valid 'reset password' link.
Users should feel free to add to this page (i.e. branding or security widgets)
but should be sure not to delete any of the form inputs or the javascript from the
template file. This javascript is what adds the necessary values to authenticate
this session with Parse.
The query params 'username' and 'app' hold the friendly names for your current user and
your app. You should feel free to incorporate their values to make the page more personal.
If you are missing form parameters in your POST, Parse will navigate back to this page and
add an 'error' query parameter.
-->
<html>
<head>
<title>Password Reset</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Reset Your Password</h1>
<noscript><p>We apologize, but resetting your password requires javascript</p></noscript>
<p>You can set a new Password for your account: {{username}}</p>
<br />
<p>{{error}}</p>
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
<input name='utf-8' type='hidden' value='✓' />
<input name="username" type="hidden" id="username" value="{{{username}}}" />
<input name="token" type="hidden" id="token" value="{{{token}}}" />
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
<p>New Password</p>
<input name="new_password" type="password" id="password" />
<p>Confirm New Password</p>
<input name="confirm_new_password" type="password" id="password_confirm" />
<br />
<p id="password_match_info"></p>
<br />
<button id="change_password">Change Password</button>
</form>
<script>
window.onload = function() {
document.getElementById("password").oninput = validatePassword;
document.getElementById("password_confirm").oninput = validatePassword;
document.getElementById("change_password").disabled = true;
function validatePassword() {
var pass2 = document.getElementById("password").value;
var pass1 = document.getElementById("password_confirm").value;
if(pass1 !== pass2) {
if(document.getElementById("password_confirm").value) {
document.getElementById("change_password").disabled = true;
document.getElementById("password_match_info").innerHTML = "Must match the previous entry";
}
} else {
document.getElementById("change_password").disabled = false;
document.getElementById("password_match_info").innerHTML = "";
}
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a password reset link with parameters
that are missing or incorrect. This can either mean the user has incorrectly
entered a link or this could be a sign of a malicious user trying to tamper
with your app.
-->
<html>
<head>
<title>Password Reset</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Invalid password reset link!</h1>
</body>
</html>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a reset password link, then submits
the form with a new password and the password gets updated successfully.
-->
<html>
<head>
<title>Password Reset</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Success!</h1>
<p>Your password has been updated.</p>
</body>
</html>

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with a security
token that is expired or incorrect. This can either mean the user has clicked
on a stale link (i.e. re-clicked on the link) or this could be a sign of a
malicious user trying to tamper with your app.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Expired verification link!</h1>
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
<input name="username" type="hidden" value="{{{username}}}">
<input name="locale" type="hidden" value="{{{locale}}}">
<button type="submit">Resend Link</button>
</form>
</body>
</html>

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with parameters
that are missing or incorrect. This can either mean the user has incorrectly
entered a link or this could be a sign of a malicious user trying to tamper
with your app.
If the link contains an expired security token (or the email has already
been verified), this page is not displayed, there is another page for that.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Invalid verification link!</h1>
</body>
</html>

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with a security
token that is expired or incorrect, then requests to receive another link,
but it fails because the username is invalid or the email has already been
verified. This can either mean the user has previously verified the email
or this could be a sign of a malicious user trying to tamper with your app.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Invalid link!</h1>
<p>No link sent. User not found or email already verified.</p>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link with a
security token that is expired, then requests to receive another email
with a new link and the email is sent successfully.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Link sent!</h1>
<p>A new link has been sent. Check your email.</p>
</body>
</html>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a verify email link and the
email gets verified successfully.
-->
<html>
<head>
<title>Email Verification</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Email verified!</h1>
<p>Successfully verified your email for account: {{username}}.</p>
</body>
</html>

View File

@@ -0,0 +1,65 @@
<!DOCTYPE html>
<!--
This page is displayed when someone clicks a valid 'reset password' link.
Users should feel free to add to this page (i.e. branding or security widgets)
but should be sure not to delete any of the form inputs or the javascript from the
template file. This javascript is what adds the necessary values to authenticate
this session with Parse.
The query params 'username' and 'app' hold the friendly names for your current user and
your app. You should feel free to incorporate their values to make the page more personal.
If you are missing form parameters in your POST, Parse will navigate back to this page and
add an 'error' query parameter.
-->
<html>
<head>
<title>Password Reset</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Reset Your Password</h1>
<noscript><p>We apologize, but resetting your password requires javascript</p></noscript>
<p>You can set a new Password for your account: {{username}}</p>
<br />
<p>{{error}}</p>
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
<input name='utf-8' type='hidden' value='✓' />
<input name="username" type="hidden" id="username" value="{{{username}}}" />
<input name="token" type="hidden" id="token" value="{{{token}}}" />
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
<p>New Password</p>
<input name="new_password" type="password" id="password" />
<p>Confirm New Password</p>
<input name="confirm_new_password" type="password" id="password_confirm" />
<br />
<p id="password_match_info"></p>
<br />
<button id="change_password">Change Password</button>
</form>
<script>
window.onload = function() {
document.getElementById("password").oninput = validatePassword;
document.getElementById("password_confirm").oninput = validatePassword;
document.getElementById("change_password").disabled = true;
function validatePassword() {
var pass2 = document.getElementById("password").value;
var pass1 = document.getElementById("password_confirm").value;
if(pass1 !== pass2) {
if(document.getElementById("password_confirm").value) {
document.getElementById("change_password").disabled = true;
document.getElementById("password_match_info").innerHTML = "Must match the previous entry";
}
} else {
document.getElementById("change_password").disabled = false;
document.getElementById("password_match_info").innerHTML = "";
}
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a password reset link with parameters
that are missing or incorrect. This can either mean the user has incorrectly
entered a link or this could be a sign of a malicious user trying to tamper
with your app.
-->
<html>
<head>
<title>Password Reset</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Invalid password reset link!</h1>
</body>
</html>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<!--
This page is displayed when a user opens a reset password link, then submits
the form with a new password and the password gets updated successfully.
-->
<html>
<head>
<title>Password Reset</title>
</head>
<body>
<h1>{{appName}}</h1>
<h1>Success!</h1>
<p>Your password has been updated.</p>
</body>
</html>