diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df346b33..7de5b236 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,11 +37,12 @@ jobs: - name: Check broken links shell: bash continue-on-error: false - # Exclude checking locales for each link 'lang=*' + # Exclude the following: non-en locales, and 3-letter keyboard redirects (/lao, /ipa) run: | set +e set +o pipefail - npx broken-link-checker http://localhost:8053/_test --recursive --ordered ---host-requests 50 -e --filter-level 3 --exclude '*/donate' --exclude '*lang=*' | tee blc.log + npx broken-link-checker http://localhost:8053/_test --recursive --ordered ---host-requests 50 -e --filter-level 3 \ + --exclude '*/donate' --exclude '*/de/*' --exclude '*/es/*' --exclude '*/fr/*' --exclude '*/ipa/*' --exclude '*/km/*' --exclude '*lang=*' --exclude '*/lao/*' | tee blc.log echo "BLC_RESULT=${PIPESTATUS[0]}" >> "$GITHUB_ENV" - name: Report on broken links diff --git a/.htaccess b/.htaccess index fa2094fe..9cb30728 100644 --- a/.htaccess +++ b/.htaccess @@ -203,43 +203,50 @@ RewriteCond "%{DOCUMENT_ROOT}/cdn/$1$2" -f RewriteRule "^cdn/(.+\.)(gif|js|png|svg)$" "/cdn/$1$2" [END] +# +# Intentionally not using regex from Validation::validate_bcp47 because we only need lang-script-region triplet +# ^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?) +# [NC] for case-insensitive +# Long regex for "BCP-47" codes = $1, rest of URL $7 onward +# + # # keyboards Install | Download | Share | bare | .json --> # # /keyboards/install/[id] to /keyboards/install.php -RewriteRule "^(en)/keyboards/install/([^/]+)$" "/_content/keyboards/install.php?id=$2&lang=$1" [END,QSA] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/keyboards/install/([^/]+)$" "/_content/keyboards/install.php?id=$7&lang=$1" [NC,END,QSA] # /keyboards/download/[id] to /keyboards/keyboard.php # This formerly redirected to a download, but we no longer need it; keep it for # legacy links -RewriteRule "^(en)/keyboards/download/([^/]+)$" "/_content/keyboards/keyboard.php?id=$2&lang=$1" [END,QSA] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/keyboards/download/([^/]+)$" "/_content/keyboards/keyboard.php?id=$7&lang=$1" [NC,END,QSA] # /keyboards/share/[id] to /keyboards/share.php # if the keyboard exists in the repo, then share.php will redirect to /keyboards/ -RewriteRule "^(en)/keyboards/share/([^/]+)$" "/_content/keyboards/share.php?id=$2&lang=$1" [END,QSA] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/keyboards/share/([^/]+)$" "/_content/keyboards/share.php?id=$7&lang=$1" [NC,END,QSA] # /keyboards/{id}.json to /keyboards/keyboard.json.php -RewriteRule "^(en)/keyboards/(?!keyboard.json)(.*)\.json$" "/_content/keyboards/keyboard.json.php?id=$2&lang=$1" [END] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/keyboards/(?!keyboard.json)(.*)\.json$" "/_content/keyboards/keyboard.json.php?id=$7&lang=$1" [NC,END] # /keyboards/{id} to /keyboards/keyboard.php -RewriteRule "^(en)/keyboards/(?!index\.php|install|keyboard|session|share)([^/]+)$" "/_content/keyboards/keyboard.php?id=$2&lang=$1" [END,QSA] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/keyboards/(?!index\.php|install|keyboard|session|share)([^/]+)$" "/_content/keyboards/keyboard.php?id=$7&lang=$1" [NC,END,QSA] # # keyboards search # # /keyboards/languages to /keyboards/index.php -RewriteRule "^(en)/keyboards/languages/(.*)" "/_content/keyboards/index.php?q=l:id:$2&lang=$1" [END,QSA] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/keyboards/languages/(.*)" "/_content/keyboards/index.php?q=l:id:$7&lang=$1" [NC,END,QSA] # /keyboards/download to /keyboards/download.php -RewriteRule "^(en)/keyboards/download(.php)?" "/_content/keyboards/download.php?lang=$1" [END,QSA] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/keyboards/download(.php)?" "/_content/keyboards/download.php?lang=$1" [NC,END,QSA] # /keyboards/legacy to /keyboards/keyboard.php -RewriteRule "^(en)/keyboards/legacy/(.*)" "/_content/keyboards/keyboard.php?legacy=$2&lang=$1" [END] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/keyboards/legacy/(.*)" "/_content/keyboards/keyboard.php?legacy=$7&lang=$1" [NC,END] # /keyboards/countries to /keyboards/index.php -RewriteRule "^(en)/keyboards/countries/(.*)" "/_content/keyboards/index.php?q=c:id:$2&lang=$1" [END] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/keyboards/countries/(.*)" "/_content/keyboards/index.php?q=c:id:$7&lang=$1" [NC,END] # @@ -247,14 +254,14 @@ RewriteRule "^(en)/keyboards/countries/(.*)" "/_content/keyboards/index.php?q=c: # # /contact/exception to /contact/exception.php -RewriteRule "^(en)/contact/exception(.php)?" "/_content/contact/exception.php?lang=$1" [END,QSA] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/contact/exception(.php)?" "/_content/contact/exception.php?lang=$1" [NC,END,QSA] -RewriteRule "^(en)/contact/exception?id=(.+)$" "/_content/contact/exception?id=$2&lang=$1" [END] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/contact/exception?id=(.+)$" "/_content/contact/exception?id=$7&lang=$1" [NC,END] # # ios # -RewriteRule "^(en)/(iphone|ipad)(/.*)?" "/_content/iphone-and-ipad/$3?lang=$1" [END,QSA] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/(iphone|ipad)(/.*)?" "/_content/iphone-and-ipad/$8?lang=$1" [NC,END,QSA] # # downloads/releases @@ -262,16 +269,16 @@ RewriteRule "^(en)/(iphone|ipad)(/.*)?" "/_content/iphone-and-ipad/$3?lang=$1" [ # releases-tier/download # note: the tier is currently ignored -RewriteRule "^(en)/downloads/releases/(alpha|beta|stable)/(.+)$" "/_content/downloads/releases/_version_downloads.php?tier=$2&version=$3&lang=$1" [END] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/downloads/releases/(alpha|beta|stable)/(.+)$" "/_content/downloads/releases/_version_downloads.php?tier=$7&version=$8&lang=$1" [NC,END] # releases-download -RewriteRule "^(en)/downloads/releases/(.+)$" "/_content/downloads/releases/_version_downloads.php?version=$2&lang=$1" [END] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/downloads/releases/(.+)$" "/_content/downloads/releases/_version_downloads.php?version=$7&lang=$1" [NC,END] # # Assets don't use lang param # -RewriteCond "%{DOCUMENT_ROOT}/_content/$2" -f -RewriteRule "^(en)/(.+)$" "/_content/$2" [END] +RewriteCond "%{DOCUMENT_ROOT}/_content/$7" -f +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/(.+)$" "/_content/$7$8" [NC,END] # TODO: do we want en/lao --> en/keyboards/basic_kbdlao? as well as /lao --> ... @@ -281,32 +288,30 @@ RewriteRule "^(en)/(.+)$" "/_content/$2" [END] # TODO: mdhost currently in a different path than help.keyman -# TODO: en --> should be a bcp-47 type 'lang' match (e.g. ab[-x][-y]) ignore case, keep it a fairly loose expression [a-z][a-z][a-z]?(-...)? - ############################################## # Rewrite file to file.md -RewriteCond "%{DOCUMENT_ROOT}/_content/$2.md" -f -RewriteRule "^(en)/(.+)$" "/_includes/includes/md/mdhost.php?file=_content/$2.md&lang=$1" [END] +RewriteCond "%{DOCUMENT_ROOT}/_content/$7.md" -f +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/(.+)$" "/_includes/includes/md/mdhost.php?file=_content/$7.md&lang=$1" [NC,END] # Rewrite file to file.php -RewriteCond "%{DOCUMENT_ROOT}/_content/$2.php" -f -RewriteCond "%{DOCUMENT_ROOT}/_content/$2.md" !-f -RewriteRule "^(en)/(.+)$" "/_content/$2.php?lang=$1" [END] +RewriteCond "%{DOCUMENT_ROOT}/_content/$7.php" -f +RewriteCond "%{DOCUMENT_ROOT}/_content/$7.md" !-f +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/(.+)$" "/_content/$7.php?lang=$1" [NC,END] # Rewrite folder/ to folder/index.md -RewriteCond "%{DOCUMENT_ROOT}/_content/$2/index.md" -f -RewriteRule "^(en)/(.+)/$" "/_includes/includes/md/mdhost.php?file=_content/$2/index.md&lang=$1" [END] +RewriteCond "%{DOCUMENT_ROOT}/_content/$7/index.md" -f +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/(.+)/$" "/_includes/includes/md/mdhost.php?file=_content/$7/index.md&lang=$1" [NC,END] # Rewrite folder/ to folder/index.php -RewriteCond "%{DOCUMENT_ROOT}/_content/$2/index.php" -f -RewriteCond "%{DOCUMENT_ROOT}/_content/$2/index.md" !-f -RewriteRule "^(en)/(.+)/$" "/_content/$2/index.php?lang=$1" [END] +RewriteCond "%{DOCUMENT_ROOT}/_content/$7/index.php" -f +RewriteCond "%{DOCUMENT_ROOT}/_content/$7/index.md" !-f +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/(.+)/$" "/_content/$7/index.php?lang=$1" [NC,END] # Root page -RewriteRule "^(en)/$" "/_content/index.php?lang=$1" [END] +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)/$" "/_content/index.php?lang=$1" [NC,END] # Finally, append the terminating slash for folders, given it is no longer # done automatically because we put DirectorySlash off -RewriteCond "%{DOCUMENT_ROOT}/_content/$2$3" -d -RewriteRule "^(en)(.*)([^/])?$" "$1$2$3/" [R,QSA] +RewriteCond "%{DOCUMENT_ROOT}/_content/$7$8" -d +RewriteRule "^(([a-z]{2,3})(-([A-Za-z]{4}))?(-([a-z]{2}|[0-9]{3}))?)(.*)([^/])?$" "$1$7$8/" [NC,R,QSA] diff --git a/_includes/2020/templates/Menu.php b/_includes/2020/templates/Menu.php index 887d5b87..3ad99656 100644 --- a/_includes/2020/templates/Menu.php +++ b/_includes/2020/templates/Menu.php @@ -3,7 +3,9 @@ namespace Keyman\Site\com\keyman\templates; + use Keyman\Site\com\keyman\Locale; use Keyman\Site\com\keyman\Util; + use Keyman\Site\com\keyman\Validation; use Keyman\Site\Common\KeymanVersion; use Keyman\Site\Common\KeymanHosts; @@ -34,40 +36,48 @@ public static function render(array $fields): void { } /** - * Generate the URL with query to change the UI language + * Modify link of the current URL for a given UI language * @param language - language tag to use + * @return string - modified URL */ private static function change_ui_language($language): string { // Parse the current URI for populating the UI dropdown $url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/'; $parts = parse_url($url); - if (!empty($parts['query'])) { - parse_str($parts['query'], $queryParams); - } else { - $queryParams = []; + $path = ''; + // Replace language if current language in path is valid BCP-47 + // Note: Validate::validate_bcp47 differs from regex in .htaccess + if (!empty($parts['path'])) { + $path = explode("/", $parts['path']); + if ($path[1] != null ) { + /* TODO: add validation back && class_exists('\\Keyman\\Site\\com\\keyman\\Validation') && + \Keyman\Site\com\keyman\Validation::validate_bcp47($path[1]) != null*/ + $path[1] = $language; + } else { + // original URL didn't have a valid BCP-47 so inert it + array_splice($path, 1, 0, $language); + } } - // Set the language query - $queryParams['lang'] = $language; - $query = http_build_query($queryParams); - - return $parts['path'] . "?" . $query; + // Rebuild the path + $newPath = implode("/", $path); + if (!empty($parts['query'])) { + // Append query + $newPath .= "?" . $parts['query']; + } + return $newPath; } /** * Generate links that correspond to the UI options - * As UI languages get added, we'll need to update this. */ private static function render_ui_list() { echo "