From 65f041af6e484c9c3c5c6df8d32a0bcdaec89d41 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 22 Sep 2026 10:37:53 +0200 Subject: [PATCH 1/2] refactor: support mac .pkg and cleanup download path references Consolidate references to downloads across various pages and DRY out repeated content. Add support for .pkg downloads when they become available (will be automatic based on content from download server). Test-bot: skip --- _content/10/index.php | 12 +- _content/11/index.php | 10 +- _content/12/index.php | 2 +- _content/13/index.php | 3 +- _content/14/index.php | 13 +- _content/15/index.php | 13 +- _content/developer/download.php | 4 +- _content/downloads/_downloads.php | 16 +- _content/downloads/all-versions/index.php | 1 - _content/downloads/index.php | 2 +- _content/downloads/pre-release/index.php | 17 +- .../downloads/releases/_version_downloads.php | 17 +- _content/linux/download.php | 2 - _content/mac/download.php | 5 +- _content/windows/download.php | 5 +- _content/windows/keyboards.php | 1 - _includes/2020/DownloadUI.php | 186 ++++++++++++++++++ _includes/2020/KeymanDownloadVersions.php | 41 ---- _includes/includes/ui/downloads.php | 116 ----------- 19 files changed, 250 insertions(+), 216 deletions(-) create mode 100644 _includes/2020/DownloadUI.php delete mode 100644 _includes/2020/KeymanDownloadVersions.php delete mode 100644 _includes/includes/ui/downloads.php diff --git a/_content/10/index.php b/_content/10/index.php index 5d4a2eca..ef5321a8 100644 --- a/_content/10/index.php +++ b/_content/10/index.php @@ -1,12 +1,12 @@

What's New in Keyman Desktop 10 for Windows?

@@ -58,7 +58,7 @@ supports Unicode 11.0 and BCP 47 language identifiers. Desktop 10 also includes additional user interface for Turkish.

What's New in Keyman 10 for macOS?

@@ -68,7 +68,7 @@ and easily install keyboard packages by double-clicking the kmp file.

Keyman for Android is also available on the Play Store.

@@ -92,7 +92,7 @@ Swift 4.0 app.

What's New in KeymanWeb 10?

@@ -104,7 +104,7 @@

Developer Software

Keyman Developer 10 allows you to create diff --git a/_content/11/index.php b/_content/11/index.php index 1482d7ac..7c88ab19 100644 --- a/_content/11/index.php +++ b/_content/11/index.php @@ -1,11 +1,11 @@

What's New in Keyman 11 for macOS?

@@ -92,7 +92,7 @@
  • Keyman for Android is available on the Play Store.
  • @@ -122,7 +122,7 @@ @@ -131,7 +131,7 @@

    Developer Software

    What's new in Keyman Developer 11?

    diff --git a/_content/12/index.php b/_content/12/index.php index 91db9d88..a784b67d 100644 --- a/_content/12/index.php +++ b/_content/12/index.php @@ -1,8 +1,8 @@ User Software

    What's New in Keyman 14 for Windows?

    @@ -98,7 +99,7 @@

    What's New in Keyman 14 for macOS?

    @@ -144,7 +145,7 @@ @@ -188,7 +189,7 @@

    What's New in KeymanWeb 14?

    @@ -208,7 +209,7 @@

    Developer Software

    What's new in Keyman Developer 14?

    diff --git a/_content/15/index.php b/_content/15/index.php index 79fc5237..89bc2e9c 100644 --- a/_content/15/index.php +++ b/_content/15/index.php @@ -1,11 +1,12 @@ User Software

    What's New in Keyman 15 for Windows?

    @@ -111,7 +112,7 @@

    What's New in Keyman 15 for macOS?

    @@ -146,7 +147,7 @@ @@ -184,7 +185,7 @@

    What's New in KeymanWeb 15?

    @@ -201,7 +202,7 @@

    Developer Software

    What's new in Keyman Developer 15?

    diff --git a/_content/developer/download.php b/_content/developer/download.php index 55853861..2b391220 100644 --- a/_content/developer/download.php +++ b/_content/developer/download.php @@ -1,8 +1,8 @@

    Keyman Developer Command Line Tools

    diff --git a/_content/downloads/_downloads.php b/_content/downloads/_downloads.php index 2bd26a40..4a7d7b89 100644 --- a/_content/downloads/_downloads.php +++ b/_content/downloads/_downloads.php @@ -1,7 +1,9 @@

    @@ -20,10 +22,10 @@


    diff --git a/_content/downloads/all-versions/index.php b/_content/downloads/all-versions/index.php index d286c7b4..1d3063ad 100644 --- a/_content/downloads/all-versions/index.php +++ b/_content/downloads/all-versions/index.php @@ -1,6 +1,5 @@ Keyman version history (all products)

    Keyman for Linux

    @@ -64,8 +65,8 @@ sudo apt install keyman onboard-keyman

    You can also sign up to access pre-release versions through Google Play.

    @@ -74,10 +75,10 @@

    Please register for the pre-release versions through the links below for Keyman beta or alpha pre-releases on your iOS device. This will grant access to the respective app version through Apple's TestFlight app, which facilitates direct installation on iOS devices.

    - + diff --git a/_content/downloads/releases/_version_downloads.php b/_content/downloads/releases/_version_downloads.php index 75c43bae..a32a5508 100644 --- a/_content/downloads/releases/_version_downloads.php +++ b/_content/downloads/releases/_version_downloads.php @@ -1,9 +1,10 @@

    Keyman for Android is also available on the Play Store.

    @@ -144,10 +145,10 @@ function array_key_first(array $arr) {

    Products for Software Developers


    diff --git a/_content/linux/download.php b/_content/linux/download.php index fb5facfa..d66ccfc2 100644 --- a/_content/linux/download.php +++ b/_content/linux/download.php @@ -1,7 +1,5 @@ Install directly from keyman.com

    Install via Homebrew

    diff --git a/_content/windows/download.php b/_content/windows/download.php index 8a408255..f77e301d 100644 --- a/_content/windows/download.php +++ b/_content/windows/download.php @@ -1,6 +1,7 @@ diff --git a/_content/windows/keyboards.php b/_content/windows/keyboards.php index ccf5f082..eb5c8e13 100644 --- a/_content/windows/keyboards.php +++ b/_content/windows/keyboards.php @@ -1,6 +1,5 @@ = 1073741824) { + $bytes = number_format($bytes / 1073741824, 2) . ' GB'; + } elseif ($bytes >= 1048576) { + $bytes = number_format($bytes / 1048576, 2) . ' MB'; + } elseif ($bytes >= 1024) { + $bytes = number_format($bytes / 1024, 2) . ' KB'; + } elseif ($bytes > 1) { + $bytes = $bytes . ' bytes'; + } elseif ($bytes == 1) { + $bytes = $bytes . ' byte'; + } else { + $bytes = '0 bytes'; + } + + return $bytes; + } + + private static function filenamePatterns($target, $tier) { + global $versions; + switch($target) { + case 'android': return 'keyman-$version.apk'; + case 'android-engine': return 'keyman-engine-android-$version.zip'; + case 'developer': return ['keymandeveloper-$version.exe', 'kmcomp-$version.exe']; + case 'ios': return 'keyman-ios-$version.ipa'; // not currently used + case 'ios-engine': return 'keyman-engine-ios-$version.zip'; + case 'linux': return ''; // not currently used + case 'mac': return ['keyman-$version.pkg','keyman-$version.dmg']; + case 'web': return 'keymanweb-$version.zip'; + case 'windows': return ['keyman-$version.exe', 'keymandesktop-$version.exe']; + default: throw new Exception("Invalid target $target"); + } + } + + private static function productName($target) { + switch($target) { + case 'android': return 'product_android'; + case 'android-engine': return 'product_engine_android'; + case 'developer': return 'product_developer'; + case 'ios': return 'product_ios'; // not currently used + case 'ios-engine': return 'product_engine_ios'; + case 'linux': return 'product_linux'; // not currently used + case 'mac': return 'product_macos'; // note mismatched platform name + case 'web': return 'product_keymanweb'; // note mismatched platform name + case 'windows': return 'product_windows'; + default: throw new Exception("Invalid target $target"); + } + } + + private static function title($target) { + switch($target) { + case 'android': return 'Keyman for Android'; + case 'android-engine': return 'Keyman Engine for Android'; + case 'developer': return 'Keyman Developer'; + case 'ios': return 'Keyman for iPhone and iPad'; // not currently used + case 'ios-engine': return 'Keyman Engine for iPhone and iPad'; + case 'linux': return 'Keyman for Linux'; // not currently used + case 'mac': return 'Keyman for mac'; + case 'web': return 'KeymanWeb'; + case 'windows': return 'Keyman for Windows'; + default: throw new Exception("Invalid target $target"); + } + } + + + public static function downloadSection($platform, $tiers = '', $target = '') { + + if($target == '') $target = $platform; + + echo sprintf("

    %s

    \n\n", + $target, _m_Downloads(DownloadUI::productName($target))); + $tiers = explode(' ',$tiers); + foreach($tiers as $tier) { + $filepatterns = DownloadUI::filenamePatterns($target, $tier); + echo DownloadUI::downloadLinks($platform, $tier, $filepatterns); + } + } + + private static function downloadLinks($platform, $tier, $filepatterns) { + global $versions; + $product = DownloadUI::productName($platform); + echo sprintf("

    %s

    \n
    \n", + KeymanHosts::Instance()->downloads_keyman_com, $platform, $tier, + _m_Downloads('all_product_releases', _m_Downloads($product), _m_Downloads($tier))); + } + + public static function downloadLargeCTA($platform, $tier) { + global $versions; + + if(empty($versions)) return false; + + $found = false; + + $filepatterns = DownloadUI::filenamePatterns($platform, $tier); + $title = DownloadUI::title($platform); + // CTA only supports the first download (which will typically be the right one?) + if(!is_array($filepatterns)) $filepatterns = [$filepatterns]; + foreach($filepatterns as $filepattern) { + $file = str_replace('$version', $versions->$platform->$tier->version, $filepattern); + $file = str_replace('$tier', $tier, $file); + + if(empty($versions->$platform->$tier->files->$file)) { + continue; + } + + $found = true; + + $fileData = $versions->$platform->$tier->files->$file; + $fileSize = DownloadUI::formatSizeUnits($fileData->size); + $host = KeymanHosts::Instance()->downloads_keyman_com; + $downloadSiteUrl = "$host/$platform/$tier/{$versions->$platform->$tier->version}/$file"; + $downloadUrl = htmlentities("/go/app/download/$platform/{$versions->$platform->$tier->version}/$tier?url=". + rawurlencode($downloadSiteUrl)); + + echo << +
    +

    $title {$versions->$platform->$tier->version}

    +

    Released: {$fileData->date}

    +

    Size: $fileSize

    +
    +
    +

    Download Now

    +
    +
    + +END; + } + + if(!$found) { + echo "

    No downloads found for $title.

    "; + return false; + } + } + + public static function iosTestflightTable() { + $testflight = 'https://itunes.apple.com/us/app/testflight/id899247664?mt=8'; + $testflight_beta_signup = 'https://testflight.apple.com/join/9W4XIoxQ'; + $testflight_alpha_signup = 'https://testflight.apple.com/join/vnCV2EiH'; + $testflightTable_cdn = cdn('img/testflight-64x64.png'); + $testflight_about = 'https://developer.apple.com/testflight/testers/'; + $testflightTable = << + Available through TestFlight + TestFlight app in the App Store + Sign up here for beta-only Keyman access. + Sign up for here alpha-only Keyman access. + +END; + return $testflightTable; + } + + } +?> \ No newline at end of file diff --git a/_includes/2020/KeymanDownloadVersions.php b/_includes/2020/KeymanDownloadVersions.php deleted file mode 100644 index 550c5616..00000000 --- a/_includes/2020/KeymanDownloadVersions.php +++ /dev/null @@ -1,41 +0,0 @@ - 'keyman-(\d+\.\d+\.\d+)\.apk', // Generally, use Play Store - 'ios' => 'keyman-ios-(\d+\.\d+\.\d+)\.ipa', // Always use App Store; included for completeness but not generally used - 'mac' => 'keyman-(\d+\.\d+\.\d+)\.dmg', - 'linux' => '-', // Linux distribution is multiple archives, use a package manager - 'windows' => 'keyman(desktop)?-(\d+\.\d+\.\d+(\.\d+)?).exe' - ]; - - static function getDownloadUrls() { - if(empty(self::$versions)) - self::$versions = @json_decode(Util::call_downloads_keyman_com('/api/version/2.0', 'downloads.keyman.com-api_version_2.0.json')); - return self::$versions; - } - - static function getDownloadUrl($platform, $tier = 'stable') { - $versions = self::getDownloadUrls(); - if(empty($versions->$platform->$tier)) { - return null; - } - $files = $versions->$platform->$tier; - $pattern = self::filenamePatterns[$platform]; - foreach($files->files as $name => $details) { - //echo $name; - if(preg_match("/^$pattern\$/", $name)) { - return KeymanHosts::Instance()->downloads_keyman_com . "/$platform/$tier/{$files->version}/{$name}"; - } - } - return null; - } - } diff --git a/_includes/includes/ui/downloads.php b/_includes/includes/ui/downloads.php deleted file mode 100644 index 29974e56..00000000 --- a/_includes/includes/ui/downloads.php +++ /dev/null @@ -1,116 +0,0 @@ -= 1073741824) { - $bytes = number_format($bytes / 1073741824, 2) . ' GB'; - } elseif ($bytes >= 1048576) { - $bytes = number_format($bytes / 1048576, 2) . ' MB'; - } elseif ($bytes >= 1024) { - $bytes = number_format($bytes / 1024, 2) . ' KB'; - } elseif ($bytes > 1) { - $bytes = $bytes . ' bytes'; - } elseif ($bytes == 1) { - $bytes = $bytes . ' byte'; - } else { - $bytes = '0 bytes'; - } - - return $bytes; - } - - function downloadSection($product, $platform, $filepattern, $tiers = '', $target = '') { - if($target == '') $target = $platform; - - echo sprintf("

    %s

    \n\n", - $target, _m_Downloads($product)); - $tiers = explode(' ',$tiers); - foreach($tiers as $tier) { - echo downloadLinks($product, $platform, $tier, $filepattern); - } - } - - function downloadLinks($product, $platform, $tier, $filepatterns) { - global $versions; - echo sprintf("

    %s

    \n
      \n", ucFirst(_m_Downloads($tier))); - if(!empty($versions->$platform->$tier)) { - if(!is_array($filepatterns)) $filepatterns = array($filepatterns); - foreach($filepatterns as $filepattern) { - $file = str_replace('$version', $versions->$platform->$tier->version, $filepattern); - $file = str_replace('$tier', $tier, $file); - - if(!empty($versions->$platform->$tier->files->$file)) { - $fileData = $versions->$platform->$tier->files->$file; - $fileSize = formatSizeUnits($fileData->size); - echo sprintf("
    • %s %s %s
    • \n", - KeymanHosts::Instance()->downloads_keyman_com , - $platform, $tier, - $versions->$platform->$tier->version, - $file, $file, $tier, - _m_Downloads('released_date_size', $fileData->date, $fileSize)); - } - } - } - echo sprintf("
    • %s
    • \n

    \n", - KeymanHosts::Instance()->downloads_keyman_com, $platform, $tier, - _m_Downloads('all_product_releases', _m_Downloads($product), _m_Downloads($tier))); - } - - function downloadLargeCTA($product, $platform, $tier, $filepattern) { - global $versions; - - if(empty($versions)) return; - $file = str_replace('$version', $versions->$platform->$tier->version, $filepattern); - $file = str_replace('$tier', $tier, $file); - - if(empty($versions->$platform->$tier->files->$file)) { - echo "

    No downloads found for $product.

    "; - return false; - } - - $fileData = $versions->$platform->$tier->files->$file; - $fileSize = formatSizeUnits($fileData->size); - $host = KeymanHosts::Instance()->downloads_keyman_com; - $downloadSiteUrl = "$host/$platform/$tier/{$versions->$platform->$tier->version}/$file"; - $downloadUrl = htmlentities("/go/app/download/$platform/{$versions->$platform->$tier->version}/$tier?url=". - rawurlencode($downloadSiteUrl)); - - echo << -
    -

    $product {$versions->$platform->$tier->version}

    -

    Released: {$fileData->date}

    -

    Size: $fileSize

    -
    -
    -

    Download Now

    -
    -
    - -END; - } - - function iosTestflightTable() { - $testflight = 'https://itunes.apple.com/us/app/testflight/id899247664?mt=8'; - $testflight_beta_signup = 'https://testflight.apple.com/join/9W4XIoxQ'; - $testflight_alpha_signup = 'https://testflight.apple.com/join/vnCV2EiH'; - $testflightTable_cdn = cdn('img/testflight-64x64.png'); - $testflight_about = 'https://developer.apple.com/testflight/testers/'; - $testflightTable = << - Available through TestFlight - TestFlight app in the App Store - Sign up here for beta-only Keyman access. - Sign up for here alpha-only Keyman access. - -END; - return $testflightTable; - } -?> \ No newline at end of file From 9195f35ce85a14a6e7fa22721f2bdf93b965ff3f Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 23 Sep 2026 09:47:12 +0200 Subject: [PATCH 2/2] fix: use kmcomp-version.zip, not .exe Co-authored-by: Ross Cruickshank --- _includes/2020/DownloadUI.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/2020/DownloadUI.php b/_includes/2020/DownloadUI.php index aff40e69..77fa5fab 100644 --- a/_includes/2020/DownloadUI.php +++ b/_includes/2020/DownloadUI.php @@ -34,7 +34,7 @@ private static function filenamePatterns($target, $tier) { switch($target) { case 'android': return 'keyman-$version.apk'; case 'android-engine': return 'keyman-engine-android-$version.zip'; - case 'developer': return ['keymandeveloper-$version.exe', 'kmcomp-$version.exe']; + case 'developer': return ['keymandeveloper-$version.exe', 'kmcomp-$version.zip']; case 'ios': return 'keyman-ios-$version.ipa'; // not currently used case 'ios-engine': return 'keyman-engine-ios-$version.zip'; case 'linux': return ''; // not currently used