diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50664f7c..e59aa936 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,19 +11,24 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4.1.5 - name: Build the docker image for keyman.com app shell: bash run: | echo "TIER_TEST" > tier.txt - ./build.sh build start + ./build.sh configure build start --debug env: fail-fast: true # # Finally, run the tests # + - name: PHP test + shell: bash + run: | + docker exec keyman-com-app sh -c "vendor/bin/phpunit --testdox" + - name: Lint shell: bash run: | @@ -31,13 +36,22 @@ jobs: - name: Check broken links shell: bash + continue-on-error: false + # Exclude checking locales for each link 'lang=*' + 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 + echo "BLC_RESULT=${PIPESTATUS[0]}" >> "$GITHUB_ENV" + + - name: Report on broken links run: | - set +e; - set +o pipefail; - npx broken-link-checker http://localhost:8053/_test --ordered --recursive --host-requests 50 -e --filter-level 3 --exclude '*/donate' | \ + set +e + set +o pipefail + cat blc.log | \ grep -E "BROKEN|Getting links from" | \ - grep -B 1 "BROKEN" - exit ${PIPESTATUS[0]} + grep -B 1 "BROKEN"; + exit "${BLC_RESULT}" - name: Check PHP errors shell: bash diff --git a/.gitignore b/.gitignore index 2775cf5c..aa73d6cd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,10 @@ cdn/deploy/ tier.txt +# unit test artifacts +blc.log +.phpunit.result.cache + vendor* /node_modules/ diff --git a/.htaccess b/.htaccess index d5c9ef4c..d7665f89 100644 --- a/.htaccess +++ b/.htaccess @@ -34,6 +34,9 @@ RewriteRule "^(macosx|macos)\b(.*)$" "/mac$2" [NC,R=301,END,QSA] # Redirect deprecated Google Plus link RewriteRule "^plus.*" "/" [NC,R=301,END,QSA] +# Redirect PHP unit tests +RewriteRule "^tests(\/.*)?" "/" [NC,R=301,END,QSA] + # /donate -> donate.keyman.com RedirectMatch 301 "^(?i)/donate(\/.*)?" "https://donate.keyman.com" @@ -93,7 +96,7 @@ RewriteRule "^keyboards/download/([^/]+)$" "/keyboards/keyboard.php?id=$1" [END, # /keyboards/share/[id] to /keyboards/share.php # if the keyboard exists in the repo, then share.php will redirect to /keyboards/ -RewriteRule "^keyboards/share/([^/]+)$" "/keyboards/share.php?id=$1" [END] +RewriteRule "^keyboards/share/([^/]+)$" "/keyboards/share.php?id=$1" [END,QSA] # /keyboards/{id}.json to /keyboards/keyboard.json.php RewriteRule "^keyboards/(?!keyboard.json)(.*)\.json$" "/keyboards/keyboard.json.php?id=$1" [END] diff --git a/Dockerfile b/Dockerfile index 71f0b396..e27aed1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ # syntax=docker/dockerfile:1 + +ARG BUILDER_CONFIGURATION="release" FROM php:7.4-apache@sha256:c9d7e608f73832673479770d66aacc8100011ec751d1905ff63fae3fe2e0ca6d AS composer-builder # Install Zip to use composer @@ -15,7 +17,14 @@ RUN composer self-update USER www-data WORKDIR /composer COPY composer.* /composer/ -RUN composer install +# Consume the build argment +ARG BUILDER_CONFIGURATION +RUN if [ "$BUILDER_CONFIGURATION" = "debug" ]; then \ + # composer install --dev deprecated + COMPOSER_NO_DEV=0 composer install ; \ + else \ + COMPOSER_NO_DEV=1 composer install ; \ + fi # Site FROM php:7.4-apache@sha256:c9d7e608f73832673479770d66aacc8100011ec751d1905ff63fae3fe2e0ca6d diff --git a/README.md b/README.md index e18f83f9..301133da 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,12 @@ shell, and from this folder, run: ./build.sh build ``` +If you'll be running tests locally, the Docker image will need to be built with dev dependencies: + +```sh +./build.sh build --debug +``` + #### Start the Docker container To start up the website, in bash, run: @@ -81,7 +87,11 @@ In bash, run: #### Running tests -To check for broken links and .php file conformance, when the site is running, +When the site is running, the test action will do the following: +* PHP unit tests +* Check .php file conformance +* Check for internal broken links + in bash, run: ```sh diff --git a/_includes/2020/templates/Foot.php b/_includes/2020/templates/Foot.php index 48d0f677..b14ac531 100644 --- a/_includes/2020/templates/Foot.php +++ b/_includes/2020/templates/Foot.php @@ -38,6 +38,7 @@ static function render(array $fields = []) {
+
Receive updates 3-4 times/year, or you can
get regular updates every 2 weeks
Privacy policy
@@ -132,6 +192,9 @@ private static function render_top_menu(object $fields): void { Donate " alt="help icon" /> +
END; // } protected static function download_box($platform) { + global $_m_KeyboardDetails; + if(!empty(self::$deprecatedBy)) { return ""; } else if(isset(self::$keyboard->platformSupport->$platform) && self::$keyboard->platformSupport->$platform != 'none') { $filename = self::$id . ".kmp"; $installLink = '/keyboards/install/' . rawurlencode(self::$id); if(!empty(self::$bcp47)) $installLink .= "?bcp47=" . rawurlencode(self::$bcp47); - $h_filename = htmlspecialchars($filename); - $platformTitle = self::platformTitles[$platform]; - return << - Install keyboard -
Installs {$h_filename} for $platformTitle on this device
+
+ {$_m_KeyboardDetails('install_keyboard')} +
{$_m_KeyboardDetails('install_keyboard_button_description', htmlspecialchars($filename), self::platformTitles[$platform])}
END; } else { return << - This keyboard is not supported on this device. You may find other options below. + {$_m_KeyboardDetails('keyboard_not_supported')}
END; } @@ -138,6 +147,7 @@ protected static function download_box($platform) { protected static function WriteWebBoxes($useDescription) { global $embed_target; + global $_m_KeyboardDetails; // only show if the jsFilename property is present in the .keyboard_info if(empty(self::$keyboard->jsFilename)) { @@ -168,11 +178,11 @@ protected static function WriteWebBoxes($useDescription) { $url = KeymanHosts::Instance()->keymanweb_com ."/#$lang,Keyboard_" . self::GetWebKeyboardId(); if($useDescription) { $description = htmlentities(self::$keyboard->name); - $description = "
Use $description in your web browser. No need to install anything.
"; - $linktext = 'Use keyboard online'; + $description = "
".$_m_KeyboardDetails("use_keyboard_button_description", $description)."
"; + $linktext = $_m_KeyboardDetails("use_keyboard_online"); } else { $description = ''; - $linktext = 'Full online editor'; + $linktext = $_m_KeyboardDetails("full_online_editor"); } return << @@ -184,13 +194,14 @@ protected static function WriteWebBoxes($useDescription) { protected static function LoadData() { global $stable_version; + global $_m_KeyboardDetails; self::$error = ""; $s = @file_get_contents(KeymanHosts::Instance()->SERVER_api_keyman_com. '/keyboard/' . rawurlencode(self::$id)); if ($s === FALSE) { // Will fail later in the script self::$error .= error_get_last()['message'] . "\n"; - self::$title = 'Failed to load keyboard package ' . self::$id; + self::$title = $_m_KeyboardDetails("failed_to_load_keyboard_package", self::$id); header('HTTP/1.0 404 Keyboard not found'); } else { $s = json_decode($s); @@ -203,20 +214,21 @@ protected static function LoadData() { self::$minVersion = isset(self::$keyboard->minKeymanVersion) ? self::$keyboard->minKeymanVersion : $stable_version; self::$license = self::map_license(isset(self::$keyboard->license) ? self::$keyboard->license : 'Unknown'); } else { - self::$error .= "Error returned from ".KeymanHosts::Instance()->api_keyman_com.": $s\n"; - self::$title = 'Failed to load keyboard package ' . self::$id; + self::$error .= $_m_KeyboardDetails("error_returned_from_api", KeymanHosts::Instance()->api_keyman_com, $s); + self::$title = $_m_KeyboardDetails("failed_to_load_keyboard_package", self::$id); header('HTTP/1.0 500 Internal Server Error'); } } if(!empty(self::$keyboard)) { if (in_array('unicode', self::$keyboard->encodings) && in_array('ansi', self::$keyboard->encodings)) - self::$keyboardEncoding = 'Unicode, Legacy (ANSI)'; + self::$keyboardEncoding = $_m_KeyboardDetails("encoding_list"); else if (in_array('unicode', self::$keyboard->encodings)) - self::$keyboardEncoding = 'Unicode'; + self::$keyboardEncoding = $_m_KeyboardDetails("unicode"); else // ansi - self::$keyboardEncoding = 'Legacy (ANSI)'; + self::$keyboardEncoding = $_m_KeyboardDetails("legacy_ansi"); + // TODO: i18n date format, but would require a lot more Dockerfile dependencies $date = new DateTime(self::$keyboard->lastModifiedDate); self::$keyboardLastModifiedDate = $date->format('Y-m-d H:i'); @@ -281,6 +293,7 @@ protected static function WriteTitle() { global $embed, $session_query_q, $embed_win, $embed_version; global $embed_target; + global $_m_KeyboardDetails; if ($embed != 'none') { $head_options += [ @@ -320,7 +333,7 @@ protected static function WriteTitle() { // If parameters are missing ... ?>

-

Keyboard package not found.

+

Tier() == KeymanHosts::TIER_DEVELOPMENT && (ini_get('display_errors') !== '0')) { @@ -333,7 +346,7 @@ protected static function WriteTitle() { ?> @@ -343,9 +356,9 @@ protected static function WriteTitle() { - Important note: - This is an obsolete version of this keyboard. Unless you have a good reason, click here to install the new version, called $dep, instead. + + " . $_m_KeyboardDetails("important_note") . " " . + $_m_KeyboardDetails("obsolete_version") . + " $dep" . $_m_KeyboardDetails("instead") . "
- +
"; @@ -375,6 +391,7 @@ protected static function WriteTitle() { protected static function WriteDownloadBoxes() { global $embed, $embed_win, $embed_version; global $embed_developer; + global $_m_KeyboardDetails; // We'll write all the different platforms here and then let Bowser determine // which box to show. This is true for both embedded and web-based viewing. @@ -394,7 +411,7 @@ protected static function WriteDownloadBoxes() { if ($embed_win && isset(self::$keyboard->minKeymanVersion) && version_compare(self::$keyboard->minKeymanVersion, $embed_version) > 0) { ?> -

Sorry, this keyboard requires Keyman minKeymanVersion ?> or higher.

+

minKeymanVersion) ?>

-

Try this keyboard

+

@@ -508,48 +526,49 @@ protected static function WriteDescription() { protected static function WriteKeyboardDetails() { global $embed_target, $session_query_q; + global $_m_KeyboardDetails; // this is html, trusted in database ?> -

Keyboard Details

+

- + - + - + - + - + - + - + - + @@ -581,31 +600,32 @@ protected static function WriteKeyboardDetails() { if(isset(self::$keyboard->packageFilename)) { ?> - + + - + - - + + - + - + related)) { ?> - + - +
Keyboard ID
Supported Platforms
Author
License
Documentation helpLink)) { ?> - href='helpLink ?>'>Keyboard help + href='helpLink ?>'>
Source sourcePath) && preg_match('/^(release|experimental)\//', self::$keyboard->sourcePath)) { @@ -558,15 +577,15 @@ protected static function WriteKeyboardDetails() { href='sourcePath) ?>'>sourcePath) ?>
Keyboard Version version) ?>
Last Updated
Package Download id ?>.kmp
Monthly Downloads
Total Downloads
Encoding
Minimum Keyman Version
Related Keyboards related as $name => $value) { @@ -615,23 +635,31 @@ protected static function WriteKeyboardDetails() { // schema. $s = @file_get_contents(KeymanHosts::Instance()->SERVER_api_keyman_com.'/keyboard/' . rawurlencode($name)); if ($s === FALSE) { - echo "$hname "; + echo "$hname "; } else { echo "$hname "; } - if (isset($value->deprecates) && $value->deprecates) echo " (deprecated) "; - if (isset($value->deprecatedBy) && $value->deprecatedBy) echo " (new version) "; + if (isset($value->deprecates) && $value->deprecates) { + echo $_m_KeyboardDetails("deprecated"); + } + if (isset($value->deprecatedBy) && $value->deprecatedBy) { + echo $_m_KeyboardDetails("new_version"); + } } ?>
Supported Languages languages)) - 3; $langs = (array) self::$keyboard->languages; @@ -641,8 +669,10 @@ protected static function WriteKeyboardDetails() { foreach($langs as $bcp47 => $detail) { if($n == 3) { - echo " Expand $count more >>"; - echo "<< Collapse "; + echo " " . + $_m_KeyboardDetails("expand_more", $count) . ""; + echo "" . + $_m_KeyboardDetails("collapse") . " "; } if (property_exists($detail, 'languageName')) { echo @@ -668,7 +698,7 @@ protected static function WriteKeyboardDetails() {
-
Scan this code to load this keyboard on another device
+
- \ No newline at end of file + +
+

The Keyman blog has regular updates, on average one post every two weeks, following all the progress of Keyman and new keyboards.

+

➡️ Subscribe to regular blog updates

\ No newline at end of file diff --git a/build.sh b/build.sh index b327f482..9c1030e2 100755 --- a/build.sh +++ b/build.sh @@ -2,7 +2,7 @@ ## START STANDARD SITE BUILD SCRIPT INCLUDE readonly THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" readonly BOOTSTRAP="$(dirname "$THIS_SCRIPT")/resources/bootstrap.inc.sh" -readonly BOOTSTRAP_VERSION=v1.0.7 +readonly BOOTSTRAP_VERSION=v1.08 [ -f "$BOOTSTRAP" ] && source "$BOOTSTRAP" || source <(curl -H "Cache-Control: no-cache" -fs https://raw.githubusercontent.com/keymanapp/shared-sites/$BOOTSTRAP_VERSION/bootstrap.inc.sh) ## END STANDARD SITE BUILD SCRIPT INCLUDE @@ -28,23 +28,40 @@ builder_describe \ builder_parse "$@" function test_docker_container() { - # TODO: lint tests + # Note: ci.yml replicates these + echo "TIER_TEST" > tier.txt - echo "---- Testing links ----" set +e; set +o pipefail; - npx broken-link-checker http://localhost:8053/_test --recursive --ordered ---host-requests 50 -e --filter-level 3 --exclude '*/donate' | \ + + builder_echo blue "---- PHP unit tests" + docker exec $KEYMAN_CONTAINER_DESC sh -c "vendor/bin/phpunit --testdox" + + # Lint .php files for obvious errors + builder_echo blue "---- Lint PHP files" + docker exec $KEYMAN_CONTAINER_DESC sh -c "find . -name '*.php' | grep -v '/vendor/' | xargs -n 1 -d '\\n' php -l" + + # NOTE: link checker runs on host rather than in docker image + # Also exclude testing locales for each link: 'lang=*' + builder_echo blue "---- Testing links" + npx broken-link-checker http://localhost:8053/_test --recursive --ordered ---host-requests 50 -e --filter-level 3 --exclude '*/donate' --exclude '*lang=*' | tee blc.log + local BLC_RESULT=${PIPESTATUS[0]} + echo ---------------------------------------------------------------------- + echo Link check summary + echo ---------------------------------------------------------------------- + cat blc.log | \ grep -E "BROKEN|Getting links from" | \ grep -B 1 "BROKEN"; - echo "Done checking links" + builder_echo blue "Done checking links" rm tier.txt + return "${BLC_RESULT}" } builder_run_action configure bootstrap_configure builder_run_action clean clean_docker_container $KEYMAN_IMAGE_NAME $KEYMAN_CONTAINER_NAME builder_run_action stop stop_docker_container $KEYMAN_IMAGE_NAME $KEYMAN_CONTAINER_NAME -builder_run_action build build_docker_container $KEYMAN_IMAGE_NAME $KEYMAN_CONTAINER_NAME +builder_run_action build build_docker_container $KEYMAN_IMAGE_NAME $KEYMAN_CONTAINER_NAME $BUILDER_CONFIGURATION builder_run_action start start_docker_container $KEYMAN_IMAGE_NAME $KEYMAN_CONTAINER_NAME $KEYMAN_CONTAINER_DESC $HOST_KEYMAN_COM $PORT_KEYMAN_COM $BUILDER_CONFIGURATION builder_run_action test test_docker_container diff --git a/cdn/dev/css/template.css b/cdn/dev/css/template.css index 68433034..92e1c5fb 100644 --- a/cdn/dev/css/template.css +++ b/cdn/dev/css/template.css @@ -498,6 +498,7 @@ span.button.disabled{ background: #fff; display: block; z-index: 5; + position: relative; } #show-phone-menu { @@ -519,7 +520,7 @@ span.button.disabled{ #header-bottom { width: 100%; height: 8px; - display: block; + display: none; } #help { @@ -554,6 +555,10 @@ span.button.disabled{ box-shadow: 0px 0px 4px rgba(185, 32, 52, 0.5); } +#help #ui-language { + position: relative; + } + /* Search Box */ .search-wrap { @@ -688,8 +693,93 @@ input[type="search"] { margin-top: -1px; } -#top-menu1 .menu-item { +/* hover menus */ + +.menu-item { float: left; +} + +.menu-item:hover, +.menu-item.menu-item-force { + z-index: 2; +} + +.menu-item:hover .menu-item-dropdown, +.menu-item.menu-item-force .menu-item-dropdown { + display: block !important; + z-index: 3; +} + +.menu-item .menu-item-sub { + float: right; +} + +.menu-item-dropdown { + /* width: 280px; */ + position: absolute; + z-index: 3; + display: none; + overflow: hidden; +} + +.menu-dropdown-inner { + position: relative; + background: #f2f2f2; + margin-bottom: 10px; + padding-bottom: 10px; + box-shadow: 1px 1px 4px #000; +} + +.menu-item-dropdown h4 { + color: #000; + border-bottom: 1px solid #000; + margin: 0px 10px; + padding-top: 10px; + font-weight: 600; + line-height: 1.5em; +} + +.menu-item-dropdown h4:first-child { + border-top: none; +} + +.menu-item-dropdown form { + width: 100%; + height: 30px; + padding: 10px 0px; +} + +.menu-item-dropdown ul { + padding-top: 10px; +} + +.menu-item-dropdown ul li { + padding: 0px; + text-align: left; + line-height: 1.8em; + width: 260px; + margin: 0px 10px; +} + +.menu-item-dropdown ul li a { + color: #B92034 !important; + font-weight: 500; + font-size: 11pt; + display: inline-block; + width: 100%; + height: 100%; + padding: 0px 10px; +} + +.menu-item-dropdown ul li:hover a { + text-decoration: underline !important; +} + +.menu-item-dropdown ul li:hover { + background: #e2e2e2; +} + +#top-menu1 .menu-item { width: 160px; height: 50px; text-align: center; @@ -708,12 +798,6 @@ input[type="search"] { background: #f2f2f2; box-shadow: 1px 1px 4px #000; position: relative; - z-index: 2; -} - -#top-menu1 .menu-item:hover .menu-item-dropdown, -#top-menu1 .menu-item.menu-item-force .menu-item-dropdown { - display: block !important; } #top-menu1 .menu-item:hover .header-triangle img, @@ -730,17 +814,32 @@ input[type="search"] { font-weight: 600; } -.header-triangle img { +#top-menu1 .header-triangle img { margin-left: 5px; height: 9px; width: 17px; background: url('../img/triangle.png') 0 0; } -#top-menu1 .menu-item .menu-item-sub { - float: right; +#top-menu1 .menu-item-dropdown { + padding: 0px 5px 5px 5px; + text-align: left; +} + +#top-menu1 .menu-dropdown-inner { + left: -5px; + width: 280px; } +#ui-language .menu-item-dropdown, +#ui-language1 .menu-item-dropdown { + top: 26px; + left: -150px; + width: 180px; +} + +/* end of hover */ + #buy, #develop { text-align: left; @@ -774,44 +873,6 @@ input[type="search"] { display: block; } -.menu-item-dropdown { - width: 280px; - position: relative; - left: -5px; - z-index: 3; - display: none; - text-align: left; - padding: 0px 5px 5px 5px; - overflow: hidden; -} - -.menu-dropdown-inner { - width: 280px; - background: #f2f2f2; - margin-bottom: 10px; - padding-bottom: 10px; - box-shadow: 1px 1px 4px #000; -} - -.menu-item-dropdown h4 { - color: #000; - border-bottom: 1px solid #000; - margin: 0px 10px; - padding-top: 10px; - font-weight: 600; - line-height: 1.5em; -} - -.menu-item-dropdown h4:first-child { - border-top: none; -} - -.menu-item-dropdown form { - width: 100%; - height: 30px; - padding: 10px 0px; -} - #language-search { float: left; width: 139px; @@ -828,36 +889,6 @@ input[type="search"] { left: -1px; } -.menu-item-dropdown ul { - padding-top: 10px; -} - -.menu-item-dropdown ul li { - padding: 0px; - text-align: left; - line-height: 1.8em; - width: 260px; - margin: 0px 10px; -} - -.menu-item-dropdown ul li a { - color: #B92034 !important; - font-weight: 500; - font-size: 11pt; - display: inline-block; - width: 100%; - height: 100%; - padding: 0px 10px; -} - -.menu-item-dropdown ul li:hover a { - text-decoration: underline !important; -} - -.menu-item-dropdown ul li:hover { - background: #e2e2e2; -} - .beta { display: inline-block; font-size: 8pt; @@ -978,19 +1009,27 @@ input[type="search"] { color: #444444; } +#low-frequency { + display: block; + text-align: center; + line-height: 1.25em; +} + #privacy-policy { + display: block; text-align: center; margin-top: 16px; } -#privacy-policy a { - display: block; +#privacy-policy a, +#low-frequency a { text-decoration: none; color: white; font-size: 12pt; } -#privacy-policy a:hover { +#privacy-policy a:hover, +#low-frequency a:hover { color: #444444; } @@ -2234,6 +2273,10 @@ button.feedback { position: fixed; } + #header-bottom { + display: block; + } + #top-menu1 .menu-item { width: 148px; } @@ -2564,6 +2607,10 @@ button.feedback { display: none; } + #header-bottom { + display: block; + } + .header { position: fixed; } diff --git a/cdn/dev/css/template1.css b/cdn/dev/css/template1.css deleted file mode 100644 index 8af68abf..00000000 --- a/cdn/dev/css/template1.css +++ /dev/null @@ -1,849 +0,0 @@ -/************************************** - * CSS RESET - * ***********************************/ - -/* http://meyerweb.com/eric/tools/css/reset/ - v2.0 | 20110126 - License: none (public domain) -*/ - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} - -html{ - font-family: 'Cabin', sans-serif; - /*font-family: 'Source Sans Pro', sans-serif;*/ -} - -/************************************** - * Page Layout CSS - * ***********************************/ - -html{ - cursor: default; - background: #F2F2F2; -} - -.wrapper{ - width: 960px; - margin-left: auto; - margin-right: auto; -} - -.button{ - float: left; - width: 200px; - height: 40px; - line-height: 40px; - border-radius: 6px; - text-align: center; - margin: 20px; - cursor: pointer; - box-shadow: 1px 1px 2px #3e3e3e; -} - -.button a{ - text-decoration: none; -} - -.button h2{ - color: #fff; -} - -#container{ - min-height: 100%; - width: 100%; -} - -.main{ - float: left; - width: 100%; -} - -.valign-outer{ - height: 100%; - width: 100%; - overflow: hidden; - display: table; - position: static; -} - -.valign-middle{ - display: table-cell; - vertical-align: middle; - width: 100%; - position: static; -} - -/********************************** - * HEADER CSS - *********************************/ - -.header{ - float: left; - width: 100%; - background: #fff; - display: block; -} - -#show-phone-menu{ - display: none; -} - -#logo{ - margin-left: 30px; - float: left; -} - -#header-bottom{ - width: 100%; - height: 8px; - display: block; -} - -#help{ - width: 130px; - position: absolute; - top: 35px; - left: 100%; - margin-left: -130px; - text-decoration: none; -} - -#help p{ - float: left; - font-size: 14pt; - color: #000; - vertical-align: top; - text-decoration: none; -} - -#help img{ - float: left; - margin-left: 10px; -} - -/************************************** - * TOP MENU 1 CSS - * ***********************************/ - -#top-menu1{ - float: left; - width: 100%; - height: 50px; - background: #fff; - position: relative; - z-index: 2; - top: -8px; - padding-top: 12px; -} - -#top-menu-icon{ - float: left; - position: absolute; - top: 13px; - left: 30px; - display: none; -} - -#top-menu-icon2{ - float: left; - position: absolute; - top: 25px; - left: 100%; - margin-left: -57px; - display: none; -} - -#top-menu1 .menu-item{ - float: left; - width: 192px; - height: 50px; - text-align: center; - line-height: 50px; - overflow-y: visible; -} - -#top-menu1 .menu-item:hover, #top-menu1 .menu-item img:hover{ - cursor: pointer; - color: #B92034; -} - -#top-menu1 .menu-item:hover h3{ - text-decoration: underline; -} - -#top-menu1 .menu-item:hover{ - background: #f2f2f2; - box-shadow: 1px 1px 4px #000; - position: relative; - z-index: 2; -} - -#top-menu1 .menu-item:hover .menu-item-dropdown{ - display: block !important; -} - -#top-menu1 .menu-item:hover .header-triangle img{ - background: url('../img/triangle.png') 17px 0; -} - -#top-menu1 .menu-item a{ - color: #000; - text-decoration: none; -} - -#top-menu1 .menu-item h3{ - font-weight: 600; -} - -.header-triangle img{ - margin-left: 5px; - height: 9px; - width: 17px; - background: url('../img/triangle.png') 0 0; -} - -#top-menu1 .menu-item .menu-item-sub{ - float: left; - width: 50%; -} - -#cart{ - text-align: right; -} - -#store:hover h3{ - text-decoration: none !important; -} - -#cart img{ - margin-right: 5px; - position: relative; - top: 2px; - width: 22px; - height: 17px; - background: url('../img/cartsprite.png') 0 0; -} - -#cart img:hover{ - background: url('../img/cartsprite.png') 22px 0; -} - -#buy{ - text-align: left; -} - -#buy h3{ - margin-left: 5px; -} - -#buy h3:hover{ - color: #B92034; - text-decoration: underline !important; -} - -#store:hover{ - box-shadow: none !important; - background: none !important; -} - -#top-menu-bottom{ - width: 100%; - height: 8px; - display: block; -} - -.menu-item-dropdown{ - width: 240px; - position: relative; - left: -5px; - z-index: 3; - display: none; - text-align: left; - padding: 0px 5px 5px 5px; - overflow: hidden; -} - -.menu-dropdown-inner{ - width: 240px; - background: #f2f2f2; - margin-bottom: 10px; - padding-bottom: 10px; - box-shadow: 1px 1px 4px #000; -} - -.menu-item-dropdown h4{ - color: #000; - border-bottom: 1px solid #000; - margin: 0px 10px; - padding-top: 10px; - font-weight: 600; - line-height: 1.5em; -} - -.menu-item-dropdown h4:first-child{ - border-top: none; -} - -.menu-item-dropdown form{ - width: 100%; - height: 30px; - padding: 10px 0px; -} - -#language-search{ - float: left; - width: 139px; - height: 28px; - border-radius: 0px; - border: solid 1px gray; - margin-left: 10px; - padding-left: 10px; -} - -#search-submit{ - float: left; - position: relative; - left: -1px; -} - -.menu-item-dropdown ul li{ - padding: 0px 10px; - text-align: left; - line-height: 1.8em; -} - -.menu-item-dropdown ul li a{ - color: #B92034 !important; - font-weight: 500; -} - -.menu-item-dropdown ul li a:hover{ - text-decoration: underline !important; -} - -#phone-menu{ - display: none; -} - -/********************************** - * FOOTER CSS - *********************************/ - -.footer{ - float: left; - width: 100%; - height: 320px; - background: #A4A8AB; -} - -.footer-third{ - float: left; - width: 320px; - height: 200px; - margin-left: 80px; - margin-right: 80px; -} - -.footer-third-title{ - margin-top: 30px; - text-align: center; - font-size: 18pt; - color: #fff; - margin-bottom: 25px; -} - -.footer-third form{ - width: 320px; -} - -.footer-third form input{ - -webkit-appearance: none; - padding: 0px; - float: left; - height: 38px; - width: 200px; - padding-left: 10px; - font-size: 14pt; - border-radius: 6px; - border-bottom-right-radius: 0px; - border-top-right-radius: 0px; - box-shadow: none; - border: 1px solid #FFFFFF; - margin-left: 10px; -} - -.footer-third form input:focus{ - outline: 0; -} - -.subscribe{ - width: 90px; - margin: 0px; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - background: #666666; - box-shadow: none; -} -.subscribe:hover{ - background: #888888; -} -.footer-third img{ - float: left; - width: 80px; - height: 50px; - margin-top: -5px; -} - -.tavultesoft a, .tavultesoft a:visited { - color: white; - text-decoration: none; -} - -.tavultesoft a:hover { - color: white; - text-decoration: underline; -} - -#facebook{ - margin-left: 40px; -} - -#facebook, #twitter, #blog{ - opacity: 1; -} - -#facebook:hover, #twitter:hover, #blog:hover{ - opacity: 0.5; -} - -.tavultesoft{ - width: 320px; - margin-left: auto; - margin-right: auto; - text-align: center; -} - -.tavultesoft p{ - color: #fff; - font-size: 12pt; - margin-top: 10px; -} - -/********************************** - * SECTION 1 CSS - *********************************/ - -.section1{ - float: left; - width: 100%; - height: 420px; - background: #000000; - background-repeat:no-repeat; - background-size: 100% auto; - display: inline; -} - -#section1-bgfamily{ - background-image: url('../img/main-family-big.jpg'); -} - -#section1-bgwater{ - background-image: url('../img/waterbg.png'); -} - -#section1-bgleaf{ - background-image: url('../img/leafbg.jpg'); -} - -#sect1-title{ - float: left; - width: 40%; - height: 420px; -} - -#sect1-title h1{ - font-size: 44pt; - line-height: 1.2em; - color: #fff; - padding: 40px; - text-shadow: 1px 1px 3px #000; -} - -#sect1-title a{ - text-align: center; - display: block; - position: relative; - top: -25px; -} - -#sect1-image{ - float: left; - width: 60%; - height: 420px; - text-align: center; -} - -#sect1-image img{ - padding: 0px; -} - -/********************************** - * SECTION 2 CSS - *********************************/ - -#section2{ - float: left; - width: 100%; - margin-bottom: 30px; - display: inline; -} - -#section2 h2{ - font-size: 20pt; - line-height: 1.2em; - padding: 40px 10px 10px 10px; -} - -#section2 h3{ - font-size: 14pt; - font-weight: 600; - padding: 0px 10px 10px 10px; -} - -#section2 p{ - padding: 0px 10px 10px 10px; - line-height: 1.2em; - font-size: 14pt; -} - -#section2 p a{ - color: #B92034; - text-decoration: underline; - cursor: pointer; - font-weight: 600; - line-height: 1.2em; -} - -#section2 li{ - padding: 0px 10px; - margin-left: 10px; - list-style: disc; - list-style-position: inside; - line-height: 1.5em; -} - -/********************************** - * PHONE MENU CSS - *********************************/ - -#show-phone-menu{ - display: none; - float: left; - margin: 10px; - position: relative; - z-index: 6; -} -#phone-menu{ - position: fixed; - top: 49px; - left: 0px; - z-index: 5; - width: 80%; - height: 100%; -} -#phone-menu-inner{ - width: 100%; - height: 100%; - overflow-y: scroll; - padding-bottom: 40%; - -webkit-overflow-scrolling:touch; - border-right: solid 4px #C1C1C1; - background: #f2f2f2; -} -.phone-menu-item{ - float: left; - width: 100%; - height: auto; - padding: 10px 0px; - background: #fff; -} -.phone-menu-item:last-child{ - margin-bottom: 60px; -} -.phone-menu-item h3{ - font-size: 14pt; - font-weight: bold; - margin: 0px 10px; - border-bottom: solid 1px #000; -} -.phone-menu-item form{ - margin-top: 10px; -} -#language-search2{ - float: left; - width: 144px; - height: 30px; - margin-left: 10px; - border-radius: 0px; - border: solid 1px gray; - padding: 0px; - padding-left: 10px; - font-size: 12pt; -} -#search-submit2{ - float: left; - position: relative; - left: -1px; - border-radius: 0px; -} -.phone-menu-item ul{ - padding: 10px; -} -.phone-menu-item ul li{ - line-height: 1.8em; -} -.phone-menu-item ul li a{ - text-decoration: underline; - color: #B92034; -} - -/************************************** - * TABLET CSS - * ***********************************/ - -@media only screen and (min-width: 768px) and (max-width: 1024px){ - .wrapper{ - width: 740px; - } - /* Header */ - .header{ - position: fixed; - } - #top-menu1 .menu-item{ - width: 148px; - } - #top-menu1 .menu-item h3{ - font-size: 10pt; - } - #logo{ - margin-bottom: 15px; - } - /* Footer */ - .footer-third{ - width: 370px; - margin-left: 0px; - margin-right: 0px; - } - /* Section 1 */ - .section1{ - margin-top: 80px; - } - #sect1-title h1{ - font-size: 36pt; - } - #sect1-image img{ - width: 90%; - } - /* Phone Menu */ - #top-menu1{ - display: none; - } - #show-phone-menu{ - display: block; - margin-top: 28px; - } - #phone-menu{ - top: 81px; - width: 40%; - } -} - -@media only screen and (min-width: 768px) and (max-width: 1024px) and (orientation: portrait){ - #sect1-title h1{ - text-align: center; - font-size: 28pt; - } -} - -/* 7 inch */ -@media all and (min-width: 600px) and (max-width: 768px){ - .wrapper{ - width: 580px; - } - /* Header */ - #top-menu1 .menu-item{ - width: 116px; - } - #top-menu1 .menu-item h3{ - font-size: 8pt; - } - /* Footer */ - .footer-third{ - width: 290px; - margin-left: 0px; - margin-right: 0px; - } - .footer-third form{ - width: 290px; - } - .footer-third form input{ - width: 170px; - } - #facebook{ - margin-left: 20px; - } - /* Section 1 */ - #sect1-title h1{ - font-size: 28pt; - text-align: center; - } - #sect1-image img{ - width: 90%; - } - /* Phone Menu */ - #show-phone-menu{ - display: block; - } -} - - - - -/************************************** - * PHONE CSS - * ***********************************/ - -@media all and (min-width: 10px) and (max-width: 600px){ - .wrapper{ - width: 320px; - } - /* Header */ - .header{ - position: fixed; - } - #top-menu1{ - display: none; - } - #help{ - display: none; - } - .header{ - height: 49px !important; - } - #logo{ - margin-top: 3px; - width: 125px; - position: fixed; - left: 50%; - margin-left: -62px; - } - /* Footer */ - .footer{ - height: auto; - } - .footer-third{ - margin-left: 0px; - margin-right: 0px; - height: auto; - } - .tavultesoft{ - float: left; - margin-top: 40px; - margin-bottom: 20px; - } - .tavultesoft img{ - display: none; - } - /* Section 1 */ - .section1{ - margin-top: 57px; - height: auto; - background-size: auto 100%; - } - #sect1-image{ - width: 100%; - height: auto; - } - #sect1-image img{ - width: 80%; - height: auto; - padding-bottom: 10px; - } - #sect1-title{ - width: 100%; - height: auto; - } - #sect1-title h1{ - font-size: 24pt; - text-align: center; - padding: 20px 10px 0px 10px; - } - #sect1-title a{ - display: none; - } - /* Phone Menu */ - #show-phone-menu{ - display: block; - } -} - -/* Landscape mode */ -@media all and (min-width: 480px) and (max-width: 600px) and (orientation: landscape){ - .wrapper{ - width: 460px; - } - /* Header */ - /* Footer */ - .footer-third{ - margin-left: 70px; - margin-right: 70px; - } - .tavultesoft{ - margin-left: 70px; - } - /* Section 1 */ -} - diff --git a/cdn/dev/img/globe.png b/cdn/dev/img/globe.png new file mode 100644 index 00000000..ceb2d60d Binary files /dev/null and b/cdn/dev/img/globe.png differ diff --git a/composer.json b/composer.json index 8654659b..74e9d317 100644 --- a/composer.json +++ b/composer.json @@ -1,24 +1,12 @@ { "name": "keymanapi/keyman.com", "require": { - "sentry/sdk": "^2.1", + "sentry/sdk": "^2.1.0", "php-http/curl-client": "^2.1", - "erusev/parsedown-extra": "^0.8.1", - "erusev/parsedown": "^1.7" + "erusev/parsedown": "^1.7", + "erusev/parsedown-extra": "^0.8.1" }, "require-dev": { "phpunit/phpunit": "^9.2" - }, - "scripts": { - "test": "vendor\\bin\\phpunit --testdox", - "check-links": [ - "Composer\\Config::disableProcessTimeout", - "npx broken-link-checker http://keyman.com.localhost --ordered --recursive --host-requests 10 -e --filter-level 3 --exclude '*/donate'" - ], - "check-docker-links": [ - "Composer\\Config::disableProcessTimeout", - "npx broken-link-checker http://localhost:8053 --ordered --recursive --host-requests 10 -e --filter-level 3 --exclude '*/donate'" - ], - "lint": "find . -name '*.php' | grep -v '/vendor/' | xargs -n 1 -d '\\n' php -l" } } diff --git a/keyboards/index.php b/keyboards/index.php index c188fa85..3f27e5bf 100644 --- a/keyboards/index.php +++ b/keyboards/index.php @@ -7,10 +7,16 @@ use Keyman\Site\com\keyman\templates\Menu; use Keyman\Site\com\keyman\templates\Body; use Keyman\Site\com\keyman\templates\Foot; + use Keyman\Site\com\keyman\Locale; + + Locale::definePageLocale('LOCALE_KEYBOARDS', 'keyboards'); + $_m = function($id, ...$args) { return Locale::m(LOCALE_KEYBOARDS, $id, ...$args); }; + function _m($id, ...$args) { return Locale::m(LOCALE_KEYBOARDS, $id, ...$args); } $head_options = [ - 'title' =>'Keyboard Search', - 'description' => 'Keyman Keyboard Search', + 'title' => _m('page_title'), + 'description' => _m('page_description'), + 'language' => isset($_SESSION['lang']) ? $_SESSION['lang'] : 'en', 'css' => [Util::cdn('css/template.css'), Util::cdn('keyboard-search/search.css')], 'js' => [Util::cdn('keyboard-search/jquery.mark.js'), Util::cdn('keyboard-search/dedicated-landing-pages.js'), Util::cdn('keyboard-search/search.js')] @@ -46,14 +52,14 @@
'> -

Keyboard Search

+

END; @@ -140,6 +151,8 @@ protected static function WriteWindowsBoxes() { } protected static function WritemacOSBoxes() { + global $_m; + $keyboard = self::$keyboard; $tier = self::$tier; @@ -173,17 +186,17 @@ protected static function WritemacOSBoxes() { $result = <<
-

If you have not yet installed Keyman for macOS, please install it first before installing the keyboard.

+

{$_m('platform_not_installed', 'Keyman for macOS')}

    -
  1. Install Keyman for macOS
  2. +
  3. {$_m('install_keyman', 'Keyman for macOS')}
  4. - Install keyboard -
    Downloads {$h['name']} for macOS.
    + {$_m('install_keyboard')} +
    {$_m('downloads_keyboard_for_platform', $h['name'], 'macOS')}
@@ -192,6 +205,8 @@ protected static function WritemacOSBoxes() { } protected static function WriteLinuxBoxes() { + global $_m; + $keyboard = self::$keyboard; $tier = self::$tier; @@ -228,18 +243,19 @@ protected static function WriteLinuxBoxes() { startAfterPageLoad_Linux(document.currentScript.dataset);
-

If you have not yet installed Keyman for Linux, please install it first before installing the keyboard.

+

{$_m('platform_not_installed', 'Keyman for Linux')}

    -
  1. Install Keyman for Linux
  2. +
  3. {$_m('install_keyman', 'Keyman for Linux')}
  4. - Install keyboard + {$_m('install_keyboard')} +
    {$_m('downloads_keyboard_for_platform', $h['name'], 'Linux')}

@@ -248,6 +264,8 @@ protected static function WriteLinuxBoxes() { } protected static function WriteAndroidBoxes() { + global $_m; + $keyboard = self::$keyboard; $tier = self::$tier; @@ -287,15 +305,14 @@ protected static function WriteAndroidBoxes() {

-

Install Keyman together with {$h['name']} keyboard through the Google Play Store:

- Install from Play Store -
Installs Keyman and {$h['name']} keyboard for Android
+

{$_m('with_play_store', $h['name'])}

+ {$_m('install_from_play_store')} +
{$_m('keyman_and_keyboard_for_platform', $h['name'], 'Android')}

-

Keyman already installed? Download just this keyboard and then install in the app. -

+

{$_m('already_installed')} {$_m('download_just_keyboard')} {$_m('and_then_install_in_the_app')}

@@ -304,6 +321,7 @@ protected static function WriteAndroidBoxes() { } protected static function WriteiOSBoxes() { + global $_m; $keyboard = self::$keyboard; $tier = self::$tier; @@ -338,17 +356,17 @@ protected static function WriteiOSBoxes() { $result = <<
-

If you have not yet installed Keyman for iPhone and iPad, please install it first before installing the keyboard.

+

{$_m('platform_not_installed', 'Keyman for iPhone and iPad')}

    -
  1. Install Keyman for iPhone and iPad
  2. +
  3. {$_m('install_keyman', 'Keyman for iPhone and iPad')}
  4. - Install keyboard -
    Downloads {$h['name']} for iPhone and iPad.
    + {$_m('install_keyboard')} +
    {$_m('downloads_keyboard_for_platform', $h['name'], 'iPhone and iPad')}
@@ -409,7 +427,7 @@ protected static function WriteTitle() { // If parameters are missing ... ?>

-

Keyboard not found.

+

Tier() == KeymanHosts::TIER_DEVELOPMENT && (ini_get('display_errors') !== '0')) { diff --git a/keyboards/session.php b/keyboards/session.php index b2153876..62f1e5d6 100644 --- a/keyboards/session.php +++ b/keyboards/session.php @@ -32,6 +32,20 @@ $embed_ios = $embed == 'ios'; $embed_developer = $embed == 'developer'; + if(isset($_REQUEST['lang'])) { + \Keyman\Site\com\keyman\Locale::setLocale($_REQUEST['lang']); + } else if (isset($_SESSION['lang'])) { + \Keyman\Site\com\keyman\Locale::setLocale($_SESSION['lang']); + } else { + // Fallback to English locale + \Keyman\Site\com\keyman\Locale::setLocale( + \Keyman\Site\com\keyman\Locale::DEFAULT_LOCALE); + } + $embed_locale = \Keyman\Site\com\keyman\Locale::currentLocales(); + if (!empty($embed_locale) && $embed_locale != \Keyman\Site\com\keyman\Locale::DEFAULT_LOCALE) { + $_SESSION['lang'] = $embed_locale[0]; + } + if($embed != 'none') { // Poor man's session control because IE embedded in downlevel Windows destroys cookie support by // default, including in existing versions of Keyman. diff --git a/keyboards/share.php b/keyboards/share.php index ee254543..4f9ac19b 100644 --- a/keyboards/share.php +++ b/keyboards/share.php @@ -4,6 +4,15 @@ require_once('./session.php'); require_once __DIR__ . '/../_includes/autoload.php'; use Keyman\Site\Common\KeymanHosts; + use Keyman\Site\com\keyman\Locale; + + Locale::definePageLocale('LOCALE_KEYBOARDS_SHARE', 'keyboards/share'); + $_m = function($id, ...$args) { + return Locale::m(LOCALE_KEYBOARDS_SHARE, $id, ...$args); + }; + function _m($id, ...$args) { + return Locale::m(LOCALE_KEYBOARDS_SHARE, $id, ...$args); + } if(!isset($_REQUEST['id'])) { header('Location: /keyboards'); @@ -38,26 +47,19 @@ function find_keyboard($id) { // Keyboard not found, so let's explain. $head_options = [ - 'title' => "Share Keyboard: $id" + 'title' => $_m('head_title', $id) ]; head($head_options); ?> -

Sharing keyboard

+

-

You probably arrived here by scanning a QRCode or opening a link -to share a keyboard from the Keyman app. We are sorry, but unfortunately, -the keyboard you are interested in, called , is not currently available from the -Keyman keyboards repository.

+

,

-

How you can get this keyboard

+

-

This keyboard has been distributed peer-to-peer rather than through -the Keyman keyboards repository, so the best way to access the keyboard -is to ask the person who shared this link or QRCode with you.

+

-

If you cannot locate the person who shared the keyboard with you, -please do feel free to ask on the -Keyman Community Forum for assistance in locating the keyboard or -a suitable alternative.

\ No newline at end of file +

+

\ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..69fc7d6b --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,8 @@ + + + + + tests + + + \ No newline at end of file diff --git a/tests/InfrastructureTest.php b/tests/InfrastructureTest.php new file mode 100644 index 00000000..8b2425b3 --- /dev/null +++ b/tests/InfrastructureTest.php @@ -0,0 +1,13 @@ +assertTrue(true); + } +}