CINXE.COM
Drini Cami | The Open Library Blog
<!DOCTYPE html> <!--[if IE 7]> <html class="ie ie7" lang="en-US"> <![endif]--> <!--[if IE 8]> <html class="ie ie8" lang="en-US"> <![endif]--> <!--[if !(IE 7) & !(IE 8)]><!--> <html lang="en-US"> <!--<![endif]--> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <title>Drini Cami | The Open Library Blog</title> <link rel="profile" href="http://gmpg.org/xfn/11" /> <link rel="pingback" href="https://blog.openlibrary.org/xmlrpc.php"> <!--[if lt IE 9]> <script src="https://blog.openlibrary.org/wp-content/themes/twentytwelve/js/html5.js" type="text/javascript"></script> <![endif]--> <meta name='robots' content='max-image-preview:large' /> <style>img:is([sizes="auto" i], [sizes^="auto," i]) { contain-intrinsic-size: 3000px 1500px }</style> <link rel='dns-prefetch' href='//archive.org' /> <link rel="alternate" type="application/rss+xml" title="The Open Library Blog » Feed" href="https://blog.openlibrary.org/feed/" /> <link rel="alternate" type="application/rss+xml" title="The Open Library Blog » Comments Feed" href="https://blog.openlibrary.org/comments/feed/" /> <link rel="alternate" type="application/rss+xml" title="The Open Library Blog » Posts by Drini Cami Feed" href="https://blog.openlibrary.org/author/drini/feed/" /> <script type="text/javascript"> /* <![CDATA[ */ window._wpemojiSettings = {"baseUrl":"https:\/\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/","ext":".png","svgUrl":"https:\/\/s.w.org\/images\/core\/emoji\/15.0.3\/svg\/","svgExt":".svg","source":{"concatemoji":"https:\/\/blog.openlibrary.org\/wp-includes\/js\/wp-emoji-release.min.js?ver=6.7.2"}}; /*! This file is auto-generated */ !function(i,n){var o,s,e;function c(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(o,JSON.stringify(t))}catch(e){}}function p(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data),r=(e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0),new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data));return t.every(function(e,t){return e===r[t]})}function u(e,t,n){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\uddfa\ud83c\uddf3","\ud83c\uddfa\u200b\ud83c\uddf3")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!n(e,"\ud83d\udc26\u200d\u2b1b","\ud83d\udc26\u200b\u2b1b")}return!1}function f(e,t,n){var r="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):i.createElement("canvas"),a=r.getContext("2d",{willReadFrequently:!0}),o=(a.textBaseline="top",a.font="600 32px Arial",{});return e.forEach(function(e){o[e]=t(a,e,n)}),o}function t(e){var t=i.createElement("script");t.src=e,t.defer=!0,i.head.appendChild(t)}"undefined"!=typeof Promise&&(o="wpEmojiSettingsSupports",s=["flag","emoji"],n.supports={everything:!0,everythingExceptFlag:!0},e=new Promise(function(e){i.addEventListener("DOMContentLoaded",e,{once:!0})}),new Promise(function(t){var n=function(){try{var e=JSON.parse(sessionStorage.getItem(o));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+f.toString()+"("+[JSON.stringify(s),u.toString(),p.toString()].join(",")+"));",r=new Blob([e],{type:"text/javascript"}),a=new Worker(URL.createObjectURL(r),{name:"wpTestEmojiSupports"});return void(a.onmessage=function(e){c(n=e.data),a.terminate(),t(n)})}catch(e){}c(n=f(s,u,p))}t(n)}).then(function(e){for(var t in e)n.supports[t]=e[t],n.supports.everything=n.supports.everything&&n.supports[t],"flag"!==t&&(n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&n.supports[t]);n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&!n.supports.flag,n.DOMReady=!1,n.readyCallback=function(){n.DOMReady=!0}}).then(function(){return e}).then(function(){var e;n.supports.everything||(n.readyCallback(),(e=n.source||{}).concatemoji?t(e.concatemoji):e.wpemoji&&e.twemoji&&(t(e.twemoji),t(e.wpemoji)))}))}((window,document),window._wpemojiSettings); /* ]]> */ </script> <link rel='stylesheet' id='animate-css' href='https://blog.openlibrary.org/wp-content/plugins/qi-blocks/assets/css/plugins/animate/animate.min.css?ver=4.1.1' type='text/css' media='all' /> <style id='wp-emoji-styles-inline-css' type='text/css'> img.wp-smiley, img.emoji { display: inline !important; border: none !important; box-shadow: none !important; height: 1em !important; width: 1em !important; margin: 0 0.07em !important; vertical-align: -0.1em !important; background: none !important; padding: 0 !important; } </style> <style id='wp-block-library-inline-css' type='text/css'> :root{--wp-admin-theme-color:#007cba;--wp-admin-theme-color--rgb:0,124,186;--wp-admin-theme-color-darker-10:#006ba1;--wp-admin-theme-color-darker-10--rgb:0,107,161;--wp-admin-theme-color-darker-20:#005a87;--wp-admin-theme-color-darker-20--rgb:0,90,135;--wp-admin-border-width-focus:2px;--wp-block-synced-color:#7a00df;--wp-block-synced-color--rgb:122,0,223;--wp-bound-block-color:var(--wp-block-synced-color)}@media (min-resolution:192dpi){:root{--wp-admin-border-width-focus:1.5px}}.wp-element-button{cursor:pointer}:root{--wp--preset--font-size--normal:16px;--wp--preset--font-size--huge:42px}:root .has-very-light-gray-background-color{background-color:#eee}:root .has-very-dark-gray-background-color{background-color:#313131}:root .has-very-light-gray-color{color:#eee}:root .has-very-dark-gray-color{color:#313131}:root .has-vivid-green-cyan-to-vivid-cyan-blue-gradient-background{background:linear-gradient(135deg,#00d084,#0693e3)}:root .has-purple-crush-gradient-background{background:linear-gradient(135deg,#34e2e4,#4721fb 50%,#ab1dfe)}:root .has-hazy-dawn-gradient-background{background:linear-gradient(135deg,#faaca8,#dad0ec)}:root .has-subdued-olive-gradient-background{background:linear-gradient(135deg,#fafae1,#67a671)}:root .has-atomic-cream-gradient-background{background:linear-gradient(135deg,#fdd79a,#004a59)}:root .has-nightshade-gradient-background{background:linear-gradient(135deg,#330968,#31cdcf)}:root .has-midnight-gradient-background{background:linear-gradient(135deg,#020381,#2874fc)}.has-regular-font-size{font-size:1em}.has-larger-font-size{font-size:2.625em}.has-normal-font-size{font-size:var(--wp--preset--font-size--normal)}.has-huge-font-size{font-size:var(--wp--preset--font-size--huge)}.has-text-align-center{text-align:center}.has-text-align-left{text-align:left}.has-text-align-right{text-align:right}#end-resizable-editor-section{display:none}.aligncenter{clear:both}.items-justified-left{justify-content:flex-start}.items-justified-center{justify-content:center}.items-justified-right{justify-content:flex-end}.items-justified-space-between{justify-content:space-between}.screen-reader-text{border:0;clip:rect(1px,1px,1px,1px);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-wrap:normal!important}.screen-reader-text:focus{background-color:#ddd;clip:auto!important;clip-path:none;color:#444;display:block;font-size:1em;height:auto;left:5px;line-height:normal;padding:15px 23px 14px;text-decoration:none;top:5px;width:auto;z-index:100000}html :where(.has-border-color){border-style:solid}html :where([style*=border-top-color]){border-top-style:solid}html :where([style*=border-right-color]){border-right-style:solid}html :where([style*=border-bottom-color]){border-bottom-style:solid}html :where([style*=border-left-color]){border-left-style:solid}html :where([style*=border-width]){border-style:solid}html :where([style*=border-top-width]){border-top-style:solid}html :where([style*=border-right-width]){border-right-style:solid}html :where([style*=border-bottom-width]){border-bottom-style:solid}html :where([style*=border-left-width]){border-left-style:solid}html :where(img[class*=wp-image-]){height:auto;max-width:100%}:where(figure){margin:0 0 1em}html :where(.is-position-sticky){--wp-admin--admin-bar--position-offset:var(--wp-admin--admin-bar--height,0px)}@media screen and (max-width:600px){html :where(.is-position-sticky){--wp-admin--admin-bar--position-offset:0px}} </style> <style id='classic-theme-styles-inline-css' type='text/css'> /*! This file is auto-generated */ .wp-block-button__link{color:#fff;background-color:#32373c;border-radius:9999px;box-shadow:none;text-decoration:none;padding:calc(.667em + 2px) calc(1.333em + 2px);font-size:1.125em}.wp-block-file__button{background:#32373c;color:#fff;text-decoration:none} </style> <link rel='stylesheet' id='archive-auth-css' href='https://blog.openlibrary.org/wp-content/plugins/archive-auth/public/css/archive-auth-public.css?ver=1.0.0' type='text/css' media='all' /> <link rel='stylesheet' id='archive_sharing_css-css' href='https://blog.openlibrary.org/wp-content/plugins/archive-sharing-widget/public/css/archive-sharing-widget.css?ver=20181212' type='text/css' media='all' /> <link rel='stylesheet' id='qi-blocks-grid-css' href='https://blog.openlibrary.org/wp-content/plugins/qi-blocks/assets/dist/grid.css?ver=1.3.5' type='text/css' media='all' /> <link rel='stylesheet' id='qi-blocks-main-css' href='https://blog.openlibrary.org/wp-content/plugins/qi-blocks/assets/dist/main.css?ver=1.3.5' type='text/css' media='all' /> <link rel='stylesheet' id='twentytwelve-style-css' href='https://blog.openlibrary.org/wp-content/themes/twentytwelve/style.css?ver=6.7.2' type='text/css' media='all' /> <link rel='stylesheet' id='child-style-css' href='https://blog.openlibrary.org/wp-content/themes/twentytwelve-child/style.css?ver=1.0.0' type='text/css' media='all' /> <link rel='stylesheet' id='twentytwelve-fonts-css' href='https://blog.openlibrary.org/wp-content/themes/twentytwelve/fonts/font-open-sans.css?ver=20230328' type='text/css' media='all' /> <link rel='stylesheet' id='twentytwelve-block-style-css' href='https://blog.openlibrary.org/wp-content/themes/twentytwelve/css/blocks.css?ver=20240812' type='text/css' media='all' /> <!--[if lt IE 9]> <link rel='stylesheet' id='twentytwelve-ie-css' href='https://blog.openlibrary.org/wp-content/themes/twentytwelve/css/ie.css?ver=20240722' type='text/css' media='all' /> <![endif]--> <style id='akismet-widget-style-inline-css' type='text/css'> .a-stats { --akismet-color-mid-green: #357b49; --akismet-color-white: #fff; --akismet-color-light-grey: #f6f7f7; max-width: 350px; width: auto; } .a-stats * { all: unset; box-sizing: border-box; } .a-stats strong { font-weight: 600; } .a-stats a.a-stats__link, .a-stats a.a-stats__link:visited, .a-stats a.a-stats__link:active { background: var(--akismet-color-mid-green); border: none; box-shadow: none; border-radius: 8px; color: var(--akismet-color-white); cursor: pointer; display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen-Sans', 'Ubuntu', 'Cantarell', 'Helvetica Neue', sans-serif; font-weight: 500; padding: 12px; text-align: center; text-decoration: none; transition: all 0.2s ease; } /* Extra specificity to deal with TwentyTwentyOne focus style */ .widget .a-stats a.a-stats__link:focus { background: var(--akismet-color-mid-green); color: var(--akismet-color-white); text-decoration: none; } .a-stats a.a-stats__link:hover { filter: brightness(110%); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06), 0 0 2px rgba(0, 0, 0, 0.16); } .a-stats .count { color: var(--akismet-color-white); display: block; font-size: 1.5em; line-height: 1.4; padding: 0 13px; white-space: nowrap; } </style> <script type="text/javascript" src="https://archive.org/includes/analytics.js?ver=ffd0d9" id="archive_analytics_js-js"></script> <script type="text/javascript" src="https://blog.openlibrary.org/wp-includes/js/jquery/jquery.min.js?ver=3.7.1" id="jquery-core-js"></script> <script type="text/javascript" src="https://blog.openlibrary.org/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.4.1" id="jquery-migrate-js"></script> <script type="text/javascript" src="https://blog.openlibrary.org/wp-content/plugins/archive-auth/public/js/archive-auth-public.js?ver=1.0.0" id="archive-auth-js"></script> <script type="text/javascript" src="https://archive.org/web/wb404.js?ver=20181212" id="archive_wayback_404_js-js"></script> <script type="text/javascript" src="https://blog.openlibrary.org/wp-content/themes/twentytwelve/js/navigation.js?ver=20141205" id="twentytwelve-navigation-js" defer="defer" data-wp-strategy="defer"></script> <link rel="https://api.w.org/" href="https://blog.openlibrary.org/wp-json/" /><link rel="alternate" title="JSON" type="application/json" href="https://blog.openlibrary.org/wp-json/wp/v2/users/1842" /><link rel="EditURI" type="application/rsd+xml" title="RSD" href="https://blog.openlibrary.org/xmlrpc.php?rsd" /> <meta name="generator" content="WordPress 6.7.2" /> <link rel="icon" href="https://blog.openlibrary.org/files/2016/02/OL-logo.jpg" sizes="32x32" /> <link rel="icon" href="https://blog.openlibrary.org/files/2016/02/OL-logo.jpg" sizes="192x192" /> <link rel="apple-touch-icon" href="https://blog.openlibrary.org/files/2016/02/OL-logo.jpg" /> <meta name="msapplication-TileImage" content="https://blog.openlibrary.org/files/2016/02/OL-logo.jpg" /> <script src="//archive.org/includes/analytics.js?v=20230130" type="text/javascript"></script> <script> 'use strict'; if ('archive_analytics' in window) { archive_analytics.service = 'blog'; archive_analytics.send_pageview_on_load({}); archive_analytics.process_url_events(window.location); } </script> <meta name="monetization" content="$ilp.uphold.com/D7BwPKMQzBiD"> </head> <body class="archive author author-drini author-1842 wp-embed-responsive qi-blocks-1.3.5 qodef-gutenberg--no-touch custom-font-enabled"> <div id="page" class="hfeed site"> <header id="masthead" class="site-header" role="banner"> <hgroup> <h1 class="site-title"><a href="https://blog.openlibrary.org/" title="The Open Library Blog" rel="home">The Open Library Blog</a></h1> <h2 class="site-description">A web page for every book</h2> </hgroup> <a href="https://blog.openlibrary.org/"> <!-- updating URL to point to 25th anniversary site, remove when done. --> <!--<a href="http://blog.archive.org/2022/09/06/building-democracys-library-celebrate-with-the-internet-archive-on-october-19/" title="Go to Democracy's Library blog post" data-event-click-tracking="DemocracysLibrary|BlogHeader" target="_blank">--> <img src="https://blog.openlibrary.org/files/2023/04/cropped-cropped-openlibrary-header.png" class="header-image" width="1280" height="437" alt="The Open Library Blog" /></a> <nav id="site-navigation" class="main-navigation" role="navigation"> <button class="menu-toggle">Menu</button> <a class="assistive-text" href="#content" title="Skip to content">Skip to content</a> <div class="menu-menu-1-container"><ul id="menu-menu-1" class="nav-menu"><li id="menu-item-1135" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-home menu-item-1135"><a href="http://blog.openlibrary.org/">Home</a></li> <li id="menu-item-1136" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-1136"><a href="https://blog.openlibrary.org/about-2/">About</a></li> </ul></div> </nav><!-- #site-navigation --> </header><!-- #masthead --> <div id="main" class="wrapper"> <section id="primary" class="site-content"> <div id="content" role="main"> <header class="archive-header"> <h1 class="archive-title"> Author Archives: <span class="vcard"><a class="url fn n" href="https://blog.openlibrary.org/author/drini/" rel="me">Drini Cami</a></span> </h1> </header><!-- .archive-header --> <article id="post-2379" class="post-2379 post type-post status-publish format-standard hentry category-accessibility category-fellowships category-internationalization-i18n category-uncategorized tag-community"> <header class="entry-header"> <h1 class="entry-title"> <a href="https://blog.openlibrary.org/2024/07/30/improving-open-librarys-translation-pipeline/" rel="bookmark">Improving Open Library’s Translation Pipeline</a> </h1> <div class="entry-byline"> Posted on <a href="https://blog.openlibrary.org/2024/07/30/improving-open-librarys-translation-pipeline/" title="4:35 pm" rel="bookmark"><time class="entry-date" datetime="2024-07-30T16:35:20+00:00">July 30, 2024</time></a><span class="by-author"> by <span class="author vcard"><a class="url fn n" href="https://blog.openlibrary.org/author/drini/" title="View all posts by Drini Cami" rel="author">Drini Cami</a></span></span> </div><!-- .entry-byline --> </header><!-- .entry-header --> <div class="entry-content"> <p></p> <p><strong>A forward</strong> by Drini Cami<br>Drini Cami here, Open Library staff developer. It’s my pleasure to introduce <a href="https://rebeccashoptaw.dev/">Rebecca Shoptaw</a>, a 2024 Open Library Engineering Fellow, to the Open Library blog in her first blog post. Rebecca began volunteering with us a few months ago and has already made many great improvements to Open Library. I’ve had the honour of mentoring her during her fellowship, and I’ve been incredibly impressed by her work and her trajectory. Combining her technical competence, work ethic, always-ready positive attitude, and her organization and attention to detail, Rebecca has been an invaluable and rare contributor. I can rely on her to take a project, break it down, learn anything she needs to learn (and fast), and then run it to completion. All while staying positive and providing clear communication of what she’s working on and not dropping any details along the way.</p> <p>In her short time here, she has also already taken a guidance role with other new contributors, improving our documentation and helping others get started. I don’t know how you found us, Rebecca, but I’m very glad you did!</p> <p>And with that, I’ll pass it to Rebecca to speak about one of her first projects on Open Library: improving our translation/internationalization pipeline.</p> <p><strong>Improving Open Library’s Translation Pipeline</strong></p> <p>Picture this: you’re browsing around on a site, not a care in the world, and suddenly out of nowhere you are told you can “<span style="text-decoration: underline;color:blue">cliquez ici</span> pour en savoir plus.” </p> <p>Maybe you know enough French to figure it out, maybe you throw it into Google Translate, maybe you can infer from the context, or maybe you just give up. In any of these cases, your experience of using the site just became that much less straightforward.</p> <p>This is what the Open Library experience has been here and there for many non-English-speaking readers. All our translation is done by volunteers, so with <a href="https://github.com/internetarchive/openlibrary/graphs/contributors">over 300 site contributors</a> and an average of <a href="https://github.com/internetarchive/openlibrary/graphs/commit-activity">40 commits added to the codebase each week</a>, there has typically been some delay between new text getting added to the site and that text being translated.</p> <p>One major source of this delay was on the developer side of the translation process. To make translation of the site possible, the developers need to provide every translator with a list of all the phrases that will be visible to readers on-screen, such as the names of buttons (“Submit,” “Cancel,” “Log In”), the links in the site menu (“My Books,” “Collections,” “Advanced Search”), and the instructions for adding and editing books, covers, and authors. While updates to the visible text occur very frequently, the translation “template” which lists all the site’s visible phrases was previously only updated manually, a process that would usually happen every 3-6 months. </p> <p>This meant that new text could sit on the site for weeks or months before our volunteer translators were able to access it for translation. There had to be a better way.</p> <p>And there was! I’m happy to report that the Open Library codebase now automatically generates that template file every time a change is made, so translators no longer have to wait. But how does it work, and how did it all happen? Let’s get into some technical details.</p> <p><strong>How It Began</strong></p> <p>Back in February, one of the site’s translators requested an update to the template file so as to begin translating some of the new text. I’d done a little developer-side translation work on the site already, so I was assigned to the issue. </p> <p>I ran the script to generate the new file, and right away noticed two things:</p> <ol class="wp-block-list"> <li>The process was very simple to run (a single command), and it ran very quickly.</li> <li>The update resulted in a 2,132-line change to the template file, which meant it had fallen very, very out of date.</li> </ol> <p>I pointed this out to the issue’s lead, Drini, and he mentioned that there had been talk of finding a way to automate the process, but they hadn’t settled on the best way to do so.</p> <p>I signed off and went to make some lunch, then ran back and suggested that the most efficient way to automate it would be to check whether each incoming change includes new/changed site text, and to run the script automatically if so. He liked the idea, so I wrote up a proposal for it, but nothing really came of it until:</p> <p><strong>The Hook</strong></p> <p>In March, Drini reached back out to me with an idea about a potentially simple way to do the automation. Whenever a developer submits a new change they would like to make to the code, we run a set of automatic tests, called “pre-commit hooks,” mostly to make sure that their submission does not contain any typos and would not cause any problems if integrated into the site. </p> <p>Since my automation idea had been to update the translation template each time a relevant change was made, Drini suggested that the most natural way to do that would be to add a quick template re-generation to the series of automated tests we already have.</p> <p>The method seemed shockingly simple, so I went ahead and drafted an implementation of it. I tested it a few times on my own computer, found that it worked like a charm, and then submitted it, only to encounter:</p> <p><strong>The Infinite Loop of Failure</strong></p> <p>Here’s where things got interesting. The first version of the script simply generated a new template file whether or not the site’s text had actually been changed – this made the most sense since the process was so fast and if nothing actually had changed in the template, the developer wouldn’t notice a difference.</p> <p>But strangely enough, even though my changes to the code didn’t include any new text, I was failing the check that I wrote! I delved into the code, did some more research into how these hooks work, and soon discovered the culprit. </p> <p>The process for a simple check and auto-fix usually works as follows:</p> <ol class="wp-block-list"> <li>When the change comes in, the automated checks run; if the program notices that something is wrong (i.e. extra whitespace), it fixes any problems automatically if possible.</li> <li>If it doesn’t notice anything wrong and/or doesn’t make any changes, it will report a success and stop there. If it notices a problem, even if it already auto-fixed it, it will report a failure and run again to make sure its fix was successful.</li> <li>On the second run, if the automatic fix was successful, the program should not have to make any further changes, and will report a success. If the program does have to make further changes, or notices that there is still a problem, it will fail again and require human intervention to fix the problem.</li> </ol> <p>This is the typical process for fixing small formatting errors that can easily be handled by an automation tool. But in this case, the script was running twice and reporting a failure both times.</p> <p>By comparing the versions of the template, I discovered that the problem was very simple: the hook is designed, as described above, to report a failure and re-run if it has made any changes to the code. The template includes a timestamp that automatically lists when it was last updated down to the second. When running online, because more pre-commit checks are run than when running locally, pre-commit takes long enough that by the time it runs again, enough seconds have elapsed that it generates a new timestamp, causing it to notice a one-line difference between the current and previous templates (the timestamp itself), and so it fails again. I.e.:</p> <ol class="wp-block-list"> <li>The changes come in, and the program auto-updates the translation template, including the timestamp.</li> <li>It notices that it has made a change (the timestamp and any new/changed phrases), so it reports a failure and runs again.</li> <li>The program auto-updates the translation template again, including the timestamp.</li> <li>It notices that it has made a change (the timestamp has changed), and reports a second failure.</li> </ol> <p>And so on. An infinite loop of failure!</p> <p>We could find no way to simply remove the timestamp from the template, so to get out of the infinite loop of failure, I ended up modifying the script so that it actually checks whether the incoming changes would affect the template before updating it. Basically, the script gathers up all the phrases in the current template and compares them to all the incoming phrases. If there is no difference, it does nothing and reports a success. If there is a difference, i.e. if the changes have added or changed the site’s text, it updates the template and reports a failure, so that now:</p> <ol class="wp-block-list"> <li>The changes come in, and the program checks whether an auto-update of the template would have any effect on the phrases. </li> <li>If there are no phrase changes, it decides not to update the template and reports a success. If there are phrase changes, it auto-updates the template, reports a failure and runs again.</li> <li>The program checks again whether an auto-update would have any effect, and this time it will not (since all the new phrases have been added), so it does not update the template or timestamp, and reports a success.</li> </ol> <p>What it looks like locally:</p> <figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="960" height="548" src="https://blog.openlibrary.org/files/2024/07/image1.gif" alt="A screen recording of the new translation script in action. A developer adds the word "Your" to the phrase "Delete Your Account" and submits the change. The automated tests run; the translation test fails, and updates the template. The developer submits the updated template change, and the automated tests run again and pass." class="wp-image-2380" /></figure> <p>I also added a few other options to the script so that developers could run it manually if they chose, and could decide whether or not to see a list of all the files that the script found translatable phrases in.</p> <p><strong>The Rollout</strong></p> <p>To ensure we were getting as much of the site’s text translated as possible, I also proposed and oversaw a bulk formatting of a lot of the onscreen text which had previously not been findable by the template-updating function. The project was heroically taken on by Meredith (<a href="https://github.com/merwhite11/">@merwhite11</a>), who successfully updated the formatting for text across almost 100 separate files. I then did a full rewrite of <a href="https://github.com/internetarchive/openlibrary/wiki/Internationalization#internationalization-i18n-developers-guide">the instructions for how to format text for translation</a>, using the lessons we learned along the way.</p> <p>When the translation automation project went live, I also wrote <a href="https://github.com/internetarchive/openlibrary/wiki/Git-Cheat-Sheet#pre-commit-and-the-github-ci">a new guide for developers</a> so they would understand what to expect when the template-updating check ran, and answered various questions from newer developers re: how the process worked.</p> <p>The next phase of the translation project involved using the same automated process we figured out to update the template to notify developers if their changes include text that isn’t correctly formatted for translation. Stef (<a href="https://github.com/pidgezero-one">@pidgezero-one</a>) did a fantastic job making that a reality, and it has allowed us to properly internationalize upwards of 500 previously untranslatable phrases, which will make internationalization much easier to keep track of for future developers.</p> <p>When I first updated the template file back in February of this year, it had not been updated since March of the previous year, about 11 months. The automation has now been live since May 1, and since then the template has already been auto-updated 35 times, or approximately every two to three days. </p> <p>While the Open Library translation process will never be perfect, I think we can be very hopeful that this automation project will make une grosse différence.</p> </div><!-- .entry-content --> <footer class="entry-meta"> Posted in <a href="https://blog.openlibrary.org/category/accessibility/" rel="category tag">accessibility</a>, <a href="https://blog.openlibrary.org/category/fellowships/" rel="category tag">Fellowships</a>, <a href="https://blog.openlibrary.org/category/internationalization-i18n/" rel="category tag">internationalization (i18n)</a>, <a href="https://blog.openlibrary.org/category/uncategorized/" rel="category tag">Uncategorized</a> | Tagged <a href="https://blog.openlibrary.org/tag/community/" rel="tag">Community</a> | <span class="comments-link"><span></span></span> </footer><!-- .entry-meta --> </article><!-- #post --> <article id="post-2177" class="post-2177 post type-post status-publish format-standard has-post-thumbnail hentry category-uncategorized tag-open-library-features tag-updates"> <header class="entry-header"> <img width="624" height="468" src="https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26.jpg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="Image of Mississauga cityscape near sunset" decoding="async" srcset="https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26.jpg 1600w, https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26-300x225.jpg 300w, https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26-500x375.jpg 500w, https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26-768x576.jpg 768w, https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26-1536x1151.jpg 1536w" sizes="(max-width: 624px) 100vw, 624px" /> <h1 class="entry-title"> <a href="https://blog.openlibrary.org/2022/12/21/search-is-getting-smarter-on-open-library/" rel="bookmark">Search Is Getting Smarter on Open Library</a> </h1> <div class="entry-byline"> Posted on <a href="https://blog.openlibrary.org/2022/12/21/search-is-getting-smarter-on-open-library/" title="1:45 am" rel="bookmark"><time class="entry-date" datetime="2022-12-21T01:45:18+00:00">December 21, 2022</time></a><span class="by-author"> by <span class="author vcard"><a class="url fn n" href="https://blog.openlibrary.org/author/drini/" title="View all posts by Drini Cami" rel="author">Drini Cami</a></span></span> </div><!-- .entry-byline --> </header><!-- .entry-header --> <div class="entry-content"> <figure class="wp-block-image size-large is-resized"><img decoding="async" src="https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26-edited.jpg" alt="Image of Mississauga cityscape near sunset. Photo credit: Bart Brewinski" class="wp-image-2185" width="521" srcset="https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26-edited.jpg 1600w, https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26-edited-300x169.jpg 300w, https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26-edited-500x281.jpg 500w, https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26-edited-768x432.jpg 768w, https://blog.openlibrary.org/files/2022/12/WhatsApp-Image-2022-12-20-at-21.00.26-edited-1536x863.jpg 1536w" sizes="(max-width: 1600px) 100vw, 1600px" /></figure> <p>Dear readers,</p> <p>I sit here, cosily on a cold winter’s night looking out over the Mississauga cityscape, thinking about the important mission we planned for and set out to accomplish almost a year ago: Empowering you, dear readers, to better search for and discover books on Open Library.</p> <p>For too many years now you’ve been limited in how books can be found from Open Library’s extensive catalogue. Since the dawn of its existence, Open Library’s goal has been to make one web page for every book ever published. And to make those books accessible! But one problem with having millions and millions of book records, is that finding just the book you need can be difficult. Search is your gateway. Your one way to find what you’re looking for. But what if search can’t get you what you need?</p> <p>Well for many readers, it was impossible to find what they were looking for. The search experience was plagued with limitations. It was impossible to find books in a certain language, or from a certain publisher. Sometimes, your search queries would even return no results at all — even for books actually in the library!</p> <p>This past week I’ve been busy rolling out our improved search experience as the default across the site. Here are the previously impossible searches that are now possible!</p> <p><strong>Find borrowable or readable books in a specific language.</strong> Previously, the results wouldn’t guarantee that a borrowable or readable edition of the search result was in the specified language. Now you can! For example, for any fellow readers who are trying to learn German, you can now easily find <a href="https://openlibrary.org/search?has_fulltext=true&mode=everything&q=language%3Ager">Borrowable or Readable books in German</a> ! Or… how about <a href="https://openlibrary.org/search?has_fulltext=true&mode=everything&q=language%3Aspa">Spanish</a>? <a href="https://openlibrary.org/search?has_fulltext=true&mode=everything&q=language%3Ajpn">Japanese</a>? <a href="https://openlibrary.org/search?has_fulltext=true&mode=everything&q=language%3Apol">Polish</a>? <a href="https://openlibrary.org/languages">Take your pick!</a></p> <p><strong>Search results now prefer editions matching your language.</strong> If you have Open Library’s language set to French and you search for “harry potter”, you will see the French cover and title of Harry Potter first. <a href="https://openlibrary.org/search?q=harry+potter&mode=everything&lang=fr">Try it!</a></p> <p><strong>Combinations of edition query fields. </strong>Now, queries can filter on edition data as well as work data. All these queries used to be impossible on Open Library:</p> <ul class="wp-block-list"><li><a href="https://openlibrary.org/search?mode=everything&q=author%3Astephen+king+language%3Afre&has_fulltext=true">Stephen King books available to read in French</a></li><li><a href="https://openlibrary.org/search?mode=everything&q=subject%3Ajuvenile+language%3Achi&has_fulltext=true">Children’s books available to read in Chinese</a></li><li><a href="https://openlibrary.org/search?q=publisher%3Alibrivox+language%3Ager&mode=everything">LibriVox audiobooks in German</a></li><li><a href="https://openlibrary.org/search?mode=everything&q=publisher%3AGraywolf+Press&has_fulltext=true">Available Graywolf Press books from the 80s</a></li></ul> <p><strong>Search results now show the edition that best matches your query.</strong> Now, if you search for “one hundred years of solitude”, because your query is in English (regardless of your display language), the English title <em>One Hundred Years of Solitude</em> will be displayed instead of the original Spanish title, <em>Cien años de soledad</em>. <a href="https://openlibrary.org/search?q=one+hundred+years+of+solitude&mode=everything">Try it!</a> Previously, searching for “one hundred years of solitude” wouldn’t match the correct book at all!</p> <p>And for any developers out there, these features are also available via the <a href="https://openlibrary.org/dev/docs/api/search">Search API</a>. You just need to add `editions` to the `fields` parameter to get back a new editions subfield with matching edition data.</p> <p>Search is a behemoth, and there’s always more to do! Here are some of the tweaks and improvements we have lined up to improve upon this work:</p> <ul class="wp-block-list"><li>Use this information in more places throughout the site<ul><li><a href="https://github.com/internetarchive/openlibrary/issues/7269">in the search autocomplete</a></li><li><a href="https://github.com/internetarchive/openlibrary/issues/7268">Make authors pages display titles in the user’s language</a></li><li><a href="https://github.com/internetarchive/openlibrary/issues/7280">Make subjects pages display titles in the user’s language</a></li><li><a href="https://github.com/internetarchive/openlibrary/issues/7281">Make the home page display covers & titles in the user’s language</a></li></ul></li><li><a href="https://github.com/internetarchive/openlibrary/issues/7274">Add a setting to decouple the UI language from the user’s preferred content language</a></li><li><a href="https://github.com/internetarchive/openlibrary/issues/7276">Fix regression where searching by author name does not match books by that author</a></li><li>And many more fixes and improvements!</li></ul> <p>These changes required an overhaul of our core <a href="https://solr.apache.org/">Solr</a>-based search infrastructure to make search results edition-aware. But now that this information is in our search engine, we just need to add it to more and more places. These are features that readers have long desired for searching Open Library. And now, their expectations are reality! Open Library just got a little easier to use, and a little more accessible and inclusive.</p> <p>Happy Reading!</p> <p>Drini (with some generous writing support and photography from Bart Brewinski)</p> <p></p> </div><!-- .entry-content --> <footer class="entry-meta"> Posted in <a href="https://blog.openlibrary.org/category/uncategorized/" rel="category tag">Uncategorized</a> | Tagged <a href="https://blog.openlibrary.org/tag/open-library-features/" rel="tag">features</a>, <a href="https://blog.openlibrary.org/tag/updates/" rel="tag">Updates</a> | <span class="comments-link"><span></span></span> </footer><!-- .entry-meta --> </article><!-- #post --> <article id="post-2054" class="post-2054 post type-post status-publish format-standard hentry category-community category-librarianship tag-books tag-open-library-features tag-updates"> <header class="entry-header"> <h1 class="entry-title"> <a href="https://blog.openlibrary.org/2021/12/20/introducing-trusted-book-providers/" rel="bookmark">Introducing Trusted Book Providers</a> </h1> <div class="entry-byline"> Posted on <a href="https://blog.openlibrary.org/2021/12/20/introducing-trusted-book-providers/" title="8:01 pm" rel="bookmark"><time class="entry-date" datetime="2021-12-20T20:01:24+00:00">December 20, 2021</time></a><span class="by-author"> by <span class="author vcard"><a class="url fn n" href="https://blog.openlibrary.org/author/drini/" title="View all posts by Drini Cami" rel="author">Drini Cami</a></span></span> </div><!-- .entry-byline --> </header><!-- .entry-header --> <div class="entry-content"> <p></p> <p>Building the Internet’s library is no easy task, and it can’t be done alone. Thankfully, we’re not alone in wanting to provide access to knowledge, books, and reading — which is why we’re excited to introduce Trusted Book Providers into Open Library. This feature allows us to provide direct “Read” links to a number of carefully selected, reputable sources of books online. Integrations with <a href="https://www.gutenberg.org/">Project Gutenberg</a> and <a href="https://librivox.org/">LibriVox</a> are up and running, and integrations with <a href="https://standardebooks.org/">Standard Ebooks</a>, <a href="https://openstax.org/">OpenStax</a>, and <a href="https://en.wikisource.org/wiki/Main_Page">Wikisource</a> are in progress. By linking to these outstanding organizations, we’re excited to help promote their wonderful work as well as give Open Library patrons easy access to more trusted sources for digital books. We see this as a step in helping the world of open access books flourish.</p> <figure class="wp-block-image size-large"><img decoding="async" width="500" height="353" src="https://blog.openlibrary.org/files/2021/12/image-1-500x353.png" alt="" class="wp-image-2091" srcset="https://blog.openlibrary.org/files/2021/12/image-1-500x353.png 500w, https://blog.openlibrary.org/files/2021/12/image-1-300x212.png 300w, https://blog.openlibrary.org/files/2021/12/image-1-768x543.png 768w, https://blog.openlibrary.org/files/2021/12/image-1.png 1129w" sizes="(max-width: 500px) 100vw, 500px" /><figcaption>Viewing LibriVox and Gutenberg works in Open Library</figcaption></figure> <hr class="wp-block-separator" /> <p>For more than ten years, Open Library has allowed patrons from across the globe to read, borrow, and listen to digital books from the Internet Archive’s prodigious <a href="https://archive.org/search.php?query=collection%3Ainlibrary">lending library</a> and public domain collection. Since then, the Internet Archive has partnered closely with more than <a href="http://blog.archive.org/2011/06/25/in-library-ebook-lending-program-expands-to-1000-libraries/">1,000 US libraries</a> to accession books, ensure their digital preservation, and make them useful to select audiences, such as those with print disabilities, through controlled library practices.</p> <p>Open Library is now excited to expand its “Read” buttons to include not only the millions of books made available by the Internet Archive, but also works from other trusted digital collections. What does this mean for patrons? It means more books and more reading options — such as LibriVox’s human-read public domain audiobooks, Standard Ebooks’ lovingly formatted modern epubs, or Project Gutenberg’s reflowable-text books. We hope this will result in a more inclusive ecosystem and shine more light on the amazing work done by these other mission-aligned non-profit organizations.</p> <h2 class="wp-block-heading">Choosing the First Trusted Book Providers</h2> <p>We selected the first group of Trusted Book Providers based on several factors. First, we prioritized non-profit organizations who are reputable, well-established, and have a similar focus on serving public good. Second, we looked for providers whose holdings increased the diversity of book formats Open Library may link to. Thirdly, we looked for providers who focus on open & permissive licensing, or public domain material.</p> <h3 class="wp-block-heading"><a href="https://www.gutenberg.org/">Project Gutenberg</a></h3> <p>Project Gutenberg is <em>the</em> oldest digital library online. Founded in <em>1971</em> (was the internet even around then?), the volunteer-driven organization is dedicated to creating free, open, long-lasting eBooks that are easily accessible from many devices. The Internet Archive already proudly preserves most of Project Gutenberg’s over 60,000 titles, and Open Library is excited to be able to have users read from Project Gutenberg directly. For patrons, the human-curated, reflowable-text formats made available by Project Gutenberg are ideal for reading on small screens, e-readers, and also for powerful accessibility customization, like dyslexic fonts and screen readers.</p> <p><a href="https://openlibrary.org/search?providerPref=gutenberg&mode=everything&q=publisher%3A%22Project+Gutenberg%22&sort=random">Browse on Open Library</a></p> <h3 class="wp-block-heading"><a href="https://librivox.org/">LibriVox</a></h3> <p>Founded in 2005, LibriVox’s stated mission is <em>“to make all books in the public domain available, narrated by real people and distributed for free, in audio format on the internet.”</em> And with over 15,000 editions in over 80 languages, they’re making great headway! The Internet Archive also works with LibriVox, and provides storage for their mass of audio files. For patrons, LibriVox integration means they will now have access to human-spoken audiobooks for many public domain works.</p> <p><a href="https://openlibrary.org/search?mode=everything&providerPref=librivox&q=publisher%3Alibrivox&sort=random">Browse on Open Library</a></p> <h3 class="wp-block-heading"><a href="https://standardebooks.org/">Standard Ebooks</a></h3> <p>Standard Ebooks is a volunteer-driven project dedicated to producing new editions of public domain ebooks that are lovingly formatted, open source, free of copyright restrictions, and free of cost. Founded in 2015, Standard Ebooks books are carefully standardized and normalized to work great as reflowable-text html, as well as modern epubs with all the trimmings — table of contents, typographical attention to detail, beautiful public domain cover art, and more. For patrons, Standard Ebooks’ over 500 titles are perfect for reading on web browsers, phones, or e-readers due to their reflowable text and modern epub features specifically optimized for every e-reader platform.</p> <p><em>In Progress… </em>| <a href="https://standardebooks.org/ebooks">Browse at Standard Ebooks</a></p> <h3 class="wp-block-heading"><a href="https://openstax.org/">OpenStax</a></h3> <p>OpenStax is a non-profit dedicated to creating original, free, open-access high school and college textbooks. Part of the non-profit corporation, Rice University, OpenStax has created over 60 high quality, peer-reviewed textbooks since its launch in 2012, with some titles available in English, Spanish, and Polish. Open Library will include OpenStax read links so our patrons can find and access these digital-only materials online or as PDF or ePub downloads.</p> <p><em>In Progress… | </em><a href="https://openstax.org/subjects">Browse at OpenStax</a></p> <h3 class="wp-block-heading"><a href="https://en.wikisource.org/wiki/Main_Page">Wikisource</a></h3> <p>Launched in 2003, Wikisource is an online digital library of free-content textual sources on a wiki, operated by the Wikimedia Foundation (the folks who run Wikipedia). Wikisource has a huge community of editors dedicated to converting scans of classic books to error-free, proofread digital books. And improving their records is as easy as editing a Wikipedia page! Offering reading options online or offline as PDF, ePub, mobi, etc for millions of records, Wikisource’s catalog, spanning over 30 languages, is unparalleled. And soon, you’ll be able to find these works right in Open Library!</p> <p><em>In Progress… </em>| <a href="https://wikisource.org/wiki/Main_Page">Browse at Wikisource</a></p> <h2 class="wp-block-heading">How Trusted Book Providers Work</h2> <p>As a patron, you shouldn’t have to do anything special to access titles from our Trusted Partners. </p> <p>When designing support for Trusted Providers, we wanted to find the right balance between <strong>convenience</strong> and <strong>trust</strong>. We didn’t want patrons to get confused by a button taking them to a new website without warning. But we also didn’t want to introduce unnecessary friction and multiple clicks preventing patrons from easily accessing books. As a result, our team team converged on two strategies:</p> <ol class="wp-block-list"><li>When a Read button is for a <strong>Trusted Provider</strong>, the button will have an external link icon like: <img loading="lazy" decoding="async" width="36" height="37" src="https://blog.openlibrary.org/files/2021/12/image.png" class="wp-image-2085" alt="" style="width: 24px;margin:0"> </li><li>When you click a <strong>Trusted Provider </strong>button, a message will appear on Open Library providing context about the Trusted Provider. The Trusted Provider link will be open within a new browser tab.</li></ol> <h2 class="wp-block-heading">Recommend a Trusted Book Provider</h2> <p>Are you a book service, library, or publisher which would like to integrate with the Open Library’s catalog? Or is there a service you’d like to recommend?</p> <p>Please <strong>recommend</strong> or <strong>apply</strong> to become a Trusted Book Provider using <a href="https://forms.gle/dAiuDD83tcCbazWp7"><strong>this form</strong></a>.</p> </div><!-- .entry-content --> <footer class="entry-meta"> Posted in <a href="https://blog.openlibrary.org/category/community/" rel="category tag">Community</a>, <a href="https://blog.openlibrary.org/category/librarianship/" rel="category tag">Librarianship</a> | Tagged <a href="https://blog.openlibrary.org/tag/books/" rel="tag">books</a>, <a href="https://blog.openlibrary.org/tag/open-library-features/" rel="tag">features</a>, <a href="https://blog.openlibrary.org/tag/updates/" rel="tag">Updates</a> | <span class="comments-link"><a href="https://blog.openlibrary.org/2021/12/20/introducing-trusted-book-providers/#comments">2 Replies</a></span> </footer><!-- .entry-meta --> </article><!-- #post --> <article id="post-1495" class="post-1495 post type-post status-publish format-standard has-post-thumbnail hentry category-bookreader category-community category-fellowships category-google-summer-of-code-gsoc category-open-source tag-open-library-gsoc tag-programs"> <header class="entry-header"> <img width="250" height="368" src="https://blog.openlibrary.org/files/2021/06/Giacomo-Cignoni-My-Internship-at-the-Internet-Archive-6-21-2021-6-03-54-PM.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="" decoding="async" loading="lazy" srcset="https://blog.openlibrary.org/files/2021/06/Giacomo-Cignoni-My-Internship-at-the-Internet-Archive-6-21-2021-6-03-54-PM.png 250w, https://blog.openlibrary.org/files/2021/06/Giacomo-Cignoni-My-Internship-at-the-Internet-Archive-6-21-2021-6-03-54-PM-204x300.png 204w" sizes="auto, (max-width: 250px) 100vw, 250px" /> <h1 class="entry-title"> <a href="https://blog.openlibrary.org/2020/08/29/giacomo-cignoni-my-internship-at-the-internet-archive/" rel="bookmark">Giacomo Cignoni: My Internship at the Internet Archive</a> </h1> <div class="entry-byline"> Posted on <a href="https://blog.openlibrary.org/2020/08/29/giacomo-cignoni-my-internship-at-the-internet-archive/" title="9:18 pm" rel="bookmark"><time class="entry-date" datetime="2020-08-29T21:18:56+00:00">August 29, 2020</time></a><span class="by-author"> by <span class="author vcard"><a class="url fn n" href="https://blog.openlibrary.org/author/drini/" title="View all posts by Drini Cami" rel="author">Drini Cami</a></span></span> </div><!-- .entry-byline --> </header><!-- .entry-header --> <div class="entry-content"> <p><em>This summer, Open Library and the Internet Archive took part in <a href="https://summerofcode.withgoogle.com/">Google Summer of Code</a> (GSoC), a Google initiative to help students gain coding experience by contributing to open source projects. I was lucky enough to mentor Giacomo while he worked on improving our BookReader experience and infrastructure. We have invited Giacomo to write a blog post to share some of the wonderful work he has done and his learnings.</em> <em>It was a pleasure working with you Giacomo, and we all wish you the best of luck with the rest of your studies!</em> <em>– Drini</em></p> <hr class="wp-block-separator" /> <p>Hi, I am <a href="https://github.com/Pyrojet99">Giacomo Cignoni</a>, a 2nd year computer science student from Italy. I submitted my 2020 Google Summer of Code (GSoC) project to work with the Internet Archive and I was selected for it. In this blogpost, I want to tell you about my experience and my accomplishments working this summer on <a href="https://github.com/internetarchive/bookreader/">BookReader</a>, Internet Archive’s open source book reading web application.</p> <a href="https://blog.openlibrary.org/2020/08/29/giacomo-cignoni-my-internship-at-the-internet-archive/#more-1495" class="more-link">Continue reading <span class="meta-nav">→</span></a> </div><!-- .entry-content --> <footer class="entry-meta"> Posted in <a href="https://blog.openlibrary.org/category/bookreader/" rel="category tag">BookReader</a>, <a href="https://blog.openlibrary.org/category/community/" rel="category tag">Community</a>, <a href="https://blog.openlibrary.org/category/fellowships/" rel="category tag">Fellowships</a>, <a href="https://blog.openlibrary.org/category/google-summer-of-code-gsoc/" rel="category tag">Google Summer of Code (GSoC)</a>, <a href="https://blog.openlibrary.org/category/open-source/" rel="category tag">Open Source</a> | Tagged <a href="https://blog.openlibrary.org/tag/open-library-gsoc/" rel="tag">GSoC</a>, <a href="https://blog.openlibrary.org/tag/programs/" rel="tag">programs</a> | <span class="comments-link"><span></span></span> </footer><!-- .entry-meta --> </article><!-- #post --> </div><!-- #content --> </section><!-- #primary --> <div id="secondary" class="widget-area" role="complementary"> <aside id="text-4" class="widget widget_text"> <div class="textwidget"><img src="http://blog.openlibrary.org/files/2016/02/Open-Library-Logo-1.jpg" alt="open library logo"></div> </aside><aside id="block-4" class="widget widget_block widget_recent_entries"><ul class="wp-block-latest-posts__list wp-block-latest-posts"><li><a class="wp-block-latest-posts__post-title" href="https://blog.openlibrary.org/2025/01/16/api-search-json-performance-tuning/">API Search.json Performance Tuning</a></li> <li><a class="wp-block-latest-posts__post-title" href="https://blog.openlibrary.org/2024/10/03/improving-search-removing-dead-ends/">Improving Search, Removing Dead-Ends</a></li> <li><a class="wp-block-latest-posts__post-title" href="https://blog.openlibrary.org/2024/07/30/improving-open-librarys-translation-pipeline/">Improving Open Library’s Translation Pipeline</a></li> <li><a class="wp-block-latest-posts__post-title" href="https://blog.openlibrary.org/2024/07/05/follow-each-other-on-open-library/">Follow each other on Open Library</a></li> <li><a class="wp-block-latest-posts__post-title" href="https://blog.openlibrary.org/2024/06/17/let-readers-read/">Let Readers Read</a></li> </ul></aside><aside id="block-6" class="widget widget_block"> <figure class="wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter"><div class="wp-block-embed__wrapper"> https://twitter.com/openlibrary </div></figure> </aside><aside id="block-7" class="widget widget_block widget_calendar"><div class="wp-block-calendar"><table id="wp-calendar" class="wp-calendar-table"> <caption>March 2025</caption> <thead> <tr> <th scope="col" title="Monday">M</th> <th scope="col" title="Tuesday">T</th> <th scope="col" title="Wednesday">W</th> <th scope="col" title="Thursday">T</th> <th scope="col" title="Friday">F</th> <th scope="col" title="Saturday">S</th> <th scope="col" title="Sunday">S</th> </tr> </thead> <tbody> <tr> <td colspan="5" class="pad"> </td><td>1</td><td id="today">2</td> </tr> <tr> <td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td> </tr> <tr> <td>10</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td> </tr> <tr> <td>17</td><td>18</td><td>19</td><td>20</td><td>21</td><td>22</td><td>23</td> </tr> <tr> <td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td><td>30</td> </tr> <tr> <td>31</td> <td class="pad" colspan="6"> </td> </tr> </tbody> </table><nav aria-label="Previous and next months" class="wp-calendar-nav"> <span class="wp-calendar-nav-prev"><a href="https://blog.openlibrary.org/2025/01/">« Jan</a></span> <span class="pad"> </span> <span class="wp-calendar-nav-next"> </span> </nav></div></aside><aside id="text-4" class="widget widget_text"> <div class="textwidget"><img src="http://blog.openlibrary.org/files/2016/02/Open-Library-Logo-1.jpg" alt="open library logo"></div> </aside> </div><!-- #secondary --> </div><!-- #main .wrapper --> <footer id="colophon" role="contentinfo"> <div class="site-info"> <a href="https://wordpress.org/" class="imprint" title="Semantic Personal Publishing Platform"> Proudly powered by WordPress </a> </div><!-- .site-info --> </footer><!-- #colophon --> </div><!-- #page --> <style id='wp-block-paragraph-inline-css' type='text/css'> .is-small-text{font-size:.875em}.is-regular-text{font-size:1em}.is-large-text{font-size:2.25em}.is-larger-text{font-size:3em}.has-drop-cap:not(:focus):first-letter{float:left;font-size:8.4em;font-style:normal;font-weight:100;line-height:.68;margin:.05em .1em 0 0;text-transform:uppercase}body.rtl .has-drop-cap:not(:focus):first-letter{float:none;margin-left:.1em}p.has-drop-cap.has-background{overflow:hidden}:root :where(p.has-background){padding:1.25em 2.375em}:where(p.has-text-color:not(.has-link-color)) a{color:inherit}p.has-text-align-left[style*="writing-mode:vertical-lr"],p.has-text-align-right[style*="writing-mode:vertical-rl"]{rotate:180deg} </style> <style id='wp-block-list-inline-css' type='text/css'> ol,ul{box-sizing:border-box}:root :where(.wp-block-list.has-background){padding:1.25em 2.375em} </style> <style id='wp-block-image-inline-css' type='text/css'> .wp-block-image a{display:inline-block}.wp-block-image img{box-sizing:border-box;height:auto;max-width:100%;vertical-align:bottom}@media (prefers-reduced-motion:no-preference){.wp-block-image img.hide{visibility:hidden}.wp-block-image img.show{animation:show-content-image .4s}}.wp-block-image[style*=border-radius] img,.wp-block-image[style*=border-radius]>a{border-radius:inherit}.wp-block-image.has-custom-border img{box-sizing:border-box}.wp-block-image.aligncenter{text-align:center}.wp-block-image.alignfull a,.wp-block-image.alignwide a{width:100%}.wp-block-image.alignfull img,.wp-block-image.alignwide img{height:auto;width:100%}.wp-block-image .aligncenter,.wp-block-image .alignleft,.wp-block-image .alignright,.wp-block-image.aligncenter,.wp-block-image.alignleft,.wp-block-image.alignright{display:table}.wp-block-image .aligncenter>figcaption,.wp-block-image .alignleft>figcaption,.wp-block-image .alignright>figcaption,.wp-block-image.aligncenter>figcaption,.wp-block-image.alignleft>figcaption,.wp-block-image.alignright>figcaption{caption-side:bottom;display:table-caption}.wp-block-image .alignleft{float:left;margin:.5em 1em .5em 0}.wp-block-image .alignright{float:right;margin:.5em 0 .5em 1em}.wp-block-image .aligncenter{margin-left:auto;margin-right:auto}.wp-block-image :where(figcaption){margin-bottom:1em;margin-top:.5em}.wp-block-image.is-style-circle-mask img{border-radius:9999px}@supports ((-webkit-mask-image:none) or (mask-image:none)) or (-webkit-mask-image:none){.wp-block-image.is-style-circle-mask img{border-radius:0;-webkit-mask-image:url('data:image/svg+xml;utf8,<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50"/></svg>');mask-image:url('data:image/svg+xml;utf8,<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50"/></svg>');mask-mode:alpha;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}}:root :where(.wp-block-image.is-style-rounded img,.wp-block-image .is-style-rounded img){border-radius:9999px}.wp-block-image figure{margin:0}.wp-lightbox-container{display:flex;flex-direction:column;position:relative}.wp-lightbox-container img{cursor:zoom-in}.wp-lightbox-container img:hover+button{opacity:1}.wp-lightbox-container button{align-items:center;-webkit-backdrop-filter:blur(16px) saturate(180%);backdrop-filter:blur(16px) saturate(180%);background-color:#5a5a5a40;border:none;border-radius:4px;cursor:zoom-in;display:flex;height:20px;justify-content:center;opacity:0;padding:0;position:absolute;right:16px;text-align:center;top:16px;transition:opacity .2s ease;width:20px;z-index:100}.wp-lightbox-container button:focus-visible{outline:3px auto #5a5a5a40;outline:3px auto -webkit-focus-ring-color;outline-offset:3px}.wp-lightbox-container button:hover{cursor:pointer;opacity:1}.wp-lightbox-container button:focus{opacity:1}.wp-lightbox-container button:focus,.wp-lightbox-container button:hover,.wp-lightbox-container button:not(:hover):not(:active):not(.has-background){background-color:#5a5a5a40;border:none}.wp-lightbox-overlay{box-sizing:border-box;cursor:zoom-out;height:100vh;left:0;overflow:hidden;position:fixed;top:0;visibility:hidden;width:100%;z-index:100000}.wp-lightbox-overlay .close-button{align-items:center;cursor:pointer;display:flex;justify-content:center;min-height:40px;min-width:40px;padding:0;position:absolute;right:calc(env(safe-area-inset-right) + 16px);top:calc(env(safe-area-inset-top) + 16px);z-index:5000000}.wp-lightbox-overlay .close-button:focus,.wp-lightbox-overlay .close-button:hover,.wp-lightbox-overlay .close-button:not(:hover):not(:active):not(.has-background){background:none;border:none}.wp-lightbox-overlay .lightbox-image-container{height:var(--wp--lightbox-container-height);left:50%;overflow:hidden;position:absolute;top:50%;transform:translate(-50%,-50%);transform-origin:top left;width:var(--wp--lightbox-container-width);z-index:9999999999}.wp-lightbox-overlay .wp-block-image{align-items:center;box-sizing:border-box;display:flex;height:100%;justify-content:center;margin:0;position:relative;transform-origin:0 0;width:100%;z-index:3000000}.wp-lightbox-overlay .wp-block-image img{height:var(--wp--lightbox-image-height);min-height:var(--wp--lightbox-image-height);min-width:var(--wp--lightbox-image-width);width:var(--wp--lightbox-image-width)}.wp-lightbox-overlay .wp-block-image figcaption{display:none}.wp-lightbox-overlay button{background:none;border:none}.wp-lightbox-overlay .scrim{background-color:#fff;height:100%;opacity:.9;position:absolute;width:100%;z-index:2000000}.wp-lightbox-overlay.active{animation:turn-on-visibility .25s both;visibility:visible}.wp-lightbox-overlay.active img{animation:turn-on-visibility .35s both}.wp-lightbox-overlay.show-closing-animation:not(.active){animation:turn-off-visibility .35s both}.wp-lightbox-overlay.show-closing-animation:not(.active) img{animation:turn-off-visibility .25s both}@media (prefers-reduced-motion:no-preference){.wp-lightbox-overlay.zoom.active{animation:none;opacity:1;visibility:visible}.wp-lightbox-overlay.zoom.active .lightbox-image-container{animation:lightbox-zoom-in .4s}.wp-lightbox-overlay.zoom.active .lightbox-image-container img{animation:none}.wp-lightbox-overlay.zoom.active .scrim{animation:turn-on-visibility .4s forwards}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active){animation:none}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container{animation:lightbox-zoom-out .4s}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .lightbox-image-container img{animation:none}.wp-lightbox-overlay.zoom.show-closing-animation:not(.active) .scrim{animation:turn-off-visibility .4s forwards}}@keyframes show-content-image{0%{visibility:hidden}99%{visibility:hidden}to{visibility:visible}}@keyframes turn-on-visibility{0%{opacity:0}to{opacity:1}}@keyframes turn-off-visibility{0%{opacity:1;visibility:visible}99%{opacity:0;visibility:visible}to{opacity:0;visibility:hidden}}@keyframes lightbox-zoom-in{0%{transform:translate(calc((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position)),calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale))}to{transform:translate(-50%,-50%) scale(1)}}@keyframes lightbox-zoom-out{0%{transform:translate(-50%,-50%) scale(1);visibility:visible}99%{visibility:visible}to{transform:translate(calc((-100vw + var(--wp--lightbox-scrollbar-width))/2 + var(--wp--lightbox-initial-left-position)),calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale));visibility:hidden}} </style> <style id='wp-block-image-theme-inline-css' type='text/css'> :root :where(.wp-block-image figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme :root :where(.wp-block-image figcaption){color:#ffffffa6}.wp-block-image{margin:0 0 1em} </style> <style id='wp-block-separator-inline-css' type='text/css'> @charset "UTF-8";.wp-block-separator{border:none;border-top:2px solid}:root :where(.wp-block-separator.is-style-dots){height:auto;line-height:1;text-align:center}:root :where(.wp-block-separator.is-style-dots):before{color:currentColor;content:"···";font-family:serif;font-size:1.5em;letter-spacing:2em;padding-left:2em}.wp-block-separator.is-style-dots{background:none!important;border:none!important} </style> <style id='wp-block-separator-theme-inline-css' type='text/css'> .wp-block-separator.has-css-opacity{opacity:.4}.wp-block-separator{border:none;border-bottom:2px solid;margin-left:auto;margin-right:auto}.wp-block-separator.has-alpha-channel-opacity{opacity:1}.wp-block-separator:not(.is-style-wide):not(.is-style-dots){width:100px}.wp-block-separator.has-background:not(.is-style-dots){border-bottom:none;height:1px}.wp-block-separator.has-background:not(.is-style-wide):not(.is-style-dots){height:2px} </style> <style id='wp-block-heading-inline-css' type='text/css'> h1.has-background,h2.has-background,h3.has-background,h4.has-background,h5.has-background,h6.has-background{padding:1.25em 2.375em}h1.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h1.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h2.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h2.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h3.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h3.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h4.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h4.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h5.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h5.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]),h6.has-text-align-left[style*=writing-mode]:where([style*=vertical-lr]),h6.has-text-align-right[style*=writing-mode]:where([style*=vertical-rl]){rotate:180deg} </style> <style id='wp-block-latest-posts-inline-css' type='text/css'> .wp-block-latest-posts{box-sizing:border-box}.wp-block-latest-posts.alignleft{margin-right:2em}.wp-block-latest-posts.alignright{margin-left:2em}.wp-block-latest-posts.wp-block-latest-posts__list{list-style:none}.wp-block-latest-posts.wp-block-latest-posts__list li{clear:both;overflow-wrap:break-word}.wp-block-latest-posts.is-grid{display:flex;flex-wrap:wrap}.wp-block-latest-posts.is-grid li{margin:0 1.25em 1.25em 0;width:100%}@media (min-width:600px){.wp-block-latest-posts.columns-2 li{width:calc(50% - .625em)}.wp-block-latest-posts.columns-2 li:nth-child(2n){margin-right:0}.wp-block-latest-posts.columns-3 li{width:calc(33.33333% - .83333em)}.wp-block-latest-posts.columns-3 li:nth-child(3n){margin-right:0}.wp-block-latest-posts.columns-4 li{width:calc(25% - .9375em)}.wp-block-latest-posts.columns-4 li:nth-child(4n){margin-right:0}.wp-block-latest-posts.columns-5 li{width:calc(20% - 1em)}.wp-block-latest-posts.columns-5 li:nth-child(5n){margin-right:0}.wp-block-latest-posts.columns-6 li{width:calc(16.66667% - 1.04167em)}.wp-block-latest-posts.columns-6 li:nth-child(6n){margin-right:0}}:root :where(.wp-block-latest-posts.is-grid){padding:0}:root :where(.wp-block-latest-posts.wp-block-latest-posts__list){padding-left:0}.wp-block-latest-posts__post-author,.wp-block-latest-posts__post-date{display:block;font-size:.8125em}.wp-block-latest-posts__post-excerpt{margin-bottom:1em;margin-top:.5em}.wp-block-latest-posts__featured-image a{display:inline-block}.wp-block-latest-posts__featured-image img{height:auto;max-width:100%;width:auto}.wp-block-latest-posts__featured-image.alignleft{float:left;margin-right:1em}.wp-block-latest-posts__featured-image.alignright{float:right;margin-left:1em}.wp-block-latest-posts__featured-image.aligncenter{margin-bottom:1em;text-align:center} </style> <style id='wp-block-embed-inline-css' type='text/css'> .wp-block-embed.alignleft,.wp-block-embed.alignright,.wp-block[data-align=left]>[data-type="core/embed"],.wp-block[data-align=right]>[data-type="core/embed"]{max-width:360px;width:100%}.wp-block-embed.alignleft .wp-block-embed__wrapper,.wp-block-embed.alignright .wp-block-embed__wrapper,.wp-block[data-align=left]>[data-type="core/embed"] .wp-block-embed__wrapper,.wp-block[data-align=right]>[data-type="core/embed"] .wp-block-embed__wrapper{min-width:280px}.wp-block-cover .wp-block-embed{min-height:240px;min-width:320px}.wp-block-embed{overflow-wrap:break-word}.wp-block-embed :where(figcaption){margin-bottom:1em;margin-top:.5em}.wp-block-embed iframe{max-width:100%}.wp-block-embed__wrapper{position:relative}.wp-embed-responsive .wp-has-aspect-ratio .wp-block-embed__wrapper:before{content:"";display:block;padding-top:50%}.wp-embed-responsive .wp-has-aspect-ratio iframe{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}.wp-embed-responsive .wp-embed-aspect-21-9 .wp-block-embed__wrapper:before{padding-top:42.85%}.wp-embed-responsive .wp-embed-aspect-18-9 .wp-block-embed__wrapper:before{padding-top:50%}.wp-embed-responsive .wp-embed-aspect-16-9 .wp-block-embed__wrapper:before{padding-top:56.25%}.wp-embed-responsive .wp-embed-aspect-4-3 .wp-block-embed__wrapper:before{padding-top:75%}.wp-embed-responsive .wp-embed-aspect-1-1 .wp-block-embed__wrapper:before{padding-top:100%}.wp-embed-responsive .wp-embed-aspect-9-16 .wp-block-embed__wrapper:before{padding-top:177.77%}.wp-embed-responsive .wp-embed-aspect-1-2 .wp-block-embed__wrapper:before{padding-top:200%} </style> <style id='wp-block-embed-theme-inline-css' type='text/css'> .wp-block-embed :where(figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme .wp-block-embed :where(figcaption){color:#ffffffa6}.wp-block-embed{margin:0 0 1em} </style> <style id='wp-block-calendar-inline-css' type='text/css'> .wp-block-calendar{text-align:center}.wp-block-calendar td,.wp-block-calendar th{border:1px solid;padding:.25em}.wp-block-calendar th{font-weight:400}.wp-block-calendar caption{background-color:inherit}.wp-block-calendar table{border-collapse:collapse;width:100%}.wp-block-calendar table:where(:not(.has-text-color)){color:#40464d}.wp-block-calendar table:where(:not(.has-text-color)) td,.wp-block-calendar table:where(:not(.has-text-color)) th{border-color:#ddd}.wp-block-calendar table.has-background th{background-color:inherit}.wp-block-calendar table.has-text-color th{color:inherit}:where(.wp-block-calendar table:not(.has-background) th){background:#ddd} </style> <style id='global-styles-inline-css' type='text/css'> :root{--wp--preset--aspect-ratio--square: 1;--wp--preset--aspect-ratio--4-3: 4/3;--wp--preset--aspect-ratio--3-4: 3/4;--wp--preset--aspect-ratio--3-2: 3/2;--wp--preset--aspect-ratio--2-3: 2/3;--wp--preset--aspect-ratio--16-9: 16/9;--wp--preset--aspect-ratio--9-16: 9/16;--wp--preset--color--black: #000000;--wp--preset--color--cyan-bluish-gray: #abb8c3;--wp--preset--color--white: #fff;--wp--preset--color--pale-pink: #f78da7;--wp--preset--color--vivid-red: #cf2e2e;--wp--preset--color--luminous-vivid-orange: #ff6900;--wp--preset--color--luminous-vivid-amber: #fcb900;--wp--preset--color--light-green-cyan: #7bdcb5;--wp--preset--color--vivid-green-cyan: #00d084;--wp--preset--color--pale-cyan-blue: #8ed1fc;--wp--preset--color--vivid-cyan-blue: #0693e3;--wp--preset--color--vivid-purple: #9b51e0;--wp--preset--color--blue: #21759b;--wp--preset--color--dark-gray: #444;--wp--preset--color--medium-gray: #9f9f9f;--wp--preset--color--light-gray: #e6e6e6;--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple: linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%);--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan: linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%);--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange: linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%);--wp--preset--gradient--luminous-vivid-orange-to-vivid-red: linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%);--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray: linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%);--wp--preset--gradient--cool-to-warm-spectrum: linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%);--wp--preset--gradient--blush-light-purple: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%);--wp--preset--gradient--blush-bordeaux: linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%);--wp--preset--gradient--luminous-dusk: linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%);--wp--preset--gradient--pale-ocean: linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%);--wp--preset--gradient--electric-grass: linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%);--wp--preset--gradient--midnight: linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%);--wp--preset--font-size--small: 13px;--wp--preset--font-size--medium: 20px;--wp--preset--font-size--large: 36px;--wp--preset--font-size--x-large: 42px;--wp--preset--spacing--20: 0.44rem;--wp--preset--spacing--30: 0.67rem;--wp--preset--spacing--40: 1rem;--wp--preset--spacing--50: 1.5rem;--wp--preset--spacing--60: 2.25rem;--wp--preset--spacing--70: 3.38rem;--wp--preset--spacing--80: 5.06rem;--wp--preset--shadow--natural: 6px 6px 9px rgba(0, 0, 0, 0.2);--wp--preset--shadow--deep: 12px 12px 50px rgba(0, 0, 0, 0.4);--wp--preset--shadow--sharp: 6px 6px 0px rgba(0, 0, 0, 0.2);--wp--preset--shadow--outlined: 6px 6px 0px -3px rgba(255, 255, 255, 1), 6px 6px rgba(0, 0, 0, 1);--wp--preset--shadow--crisp: 6px 6px 0px rgba(0, 0, 0, 1);}:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:where(.wp-block-columns.is-layout-flex){gap: 2em;}:where(.wp-block-columns.is-layout-grid){gap: 2em;}:where(.wp-block-post-template.is-layout-flex){gap: 1.25em;}:where(.wp-block-post-template.is-layout-grid){gap: 1.25em;}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-color{color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-color{color: var(--wp--preset--color--white) !important;}.has-pale-pink-color{color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-color{color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-color{color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-color{color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-color{color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-color{color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-color{color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-color{color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-color{color: var(--wp--preset--color--vivid-purple) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-background-color{background-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-pale-pink-background-color{background-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-background-color{background-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-background-color{background-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-background-color{background-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-background-color{background-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-background-color{background-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-background-color{background-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-background-color{background-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-background-color{background-color: var(--wp--preset--color--vivid-purple) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-border-color{border-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-pale-pink-border-color{border-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-border-color{border-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-border-color{border-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-border-color{border-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-border-color{border-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-border-color{border-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-border-color{border-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-border-color{border-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-border-color{border-color: var(--wp--preset--color--vivid-purple) !important;}.has-vivid-cyan-blue-to-vivid-purple-gradient-background{background: var(--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple) !important;}.has-light-green-cyan-to-vivid-green-cyan-gradient-background{background: var(--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan) !important;}.has-luminous-vivid-amber-to-luminous-vivid-orange-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange) !important;}.has-luminous-vivid-orange-to-vivid-red-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-orange-to-vivid-red) !important;}.has-very-light-gray-to-cyan-bluish-gray-gradient-background{background: var(--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray) !important;}.has-cool-to-warm-spectrum-gradient-background{background: var(--wp--preset--gradient--cool-to-warm-spectrum) !important;}.has-blush-light-purple-gradient-background{background: var(--wp--preset--gradient--blush-light-purple) !important;}.has-blush-bordeaux-gradient-background{background: var(--wp--preset--gradient--blush-bordeaux) !important;}.has-luminous-dusk-gradient-background{background: var(--wp--preset--gradient--luminous-dusk) !important;}.has-pale-ocean-gradient-background{background: var(--wp--preset--gradient--pale-ocean) !important;}.has-electric-grass-gradient-background{background: var(--wp--preset--gradient--electric-grass) !important;}.has-midnight-gradient-background{background: var(--wp--preset--gradient--midnight) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-medium-font-size{font-size: var(--wp--preset--font-size--medium) !important;}.has-large-font-size{font-size: var(--wp--preset--font-size--large) !important;}.has-x-large-font-size{font-size: var(--wp--preset--font-size--x-large) !important;} </style> <script type="text/javascript" id="qi-blocks-main-js-extra"> /* <![CDATA[ */ var qiBlocks = {"vars":{"arrowLeftIcon":"<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" x=\"0px\" y=\"0px\" viewBox=\"0 0 34.2 32.3\" xml:space=\"preserve\" style=\"stroke-width: 2;\"><line x1=\"0.5\" y1=\"16\" x2=\"33.5\" y2=\"16\"\/><line x1=\"0.3\" y1=\"16.5\" x2=\"16.2\" y2=\"0.7\"\/><line x1=\"0\" y1=\"15.4\" x2=\"16.2\" y2=\"31.6\"\/><\/svg>","arrowRightIcon":"<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" x=\"0px\" y=\"0px\" viewBox=\"0 0 34.2 32.3\" xml:space=\"preserve\" style=\"stroke-width: 2;\"><line x1=\"0\" y1=\"16\" x2=\"33\" y2=\"16\"\/><line x1=\"17.3\" y1=\"0.7\" x2=\"33.2\" y2=\"16.5\"\/><line x1=\"17.3\" y1=\"31.6\" x2=\"33.5\" y2=\"15.4\"\/><\/svg>","closeIcon":"<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" x=\"0px\" y=\"0px\" viewBox=\"0 0 9.1 9.1\" xml:space=\"preserve\"><g><path d=\"M8.5,0L9,0.6L5.1,4.5L9,8.5L8.5,9L4.5,5.1L0.6,9L0,8.5L4,4.5L0,0.6L0.6,0L4.5,4L8.5,0z\"\/><\/g><\/svg>","viewCartText":"View Cart"}}; /* ]]> */ </script> <script type="text/javascript" src="https://blog.openlibrary.org/wp-content/plugins/qi-blocks/assets/dist/main.js?ver=1.3.5" id="qi-blocks-main-js"></script> </body> </html> <!-- Dynamic page generated in 0.417 seconds. --> <!-- Cached page generated by WP-Super-Cache on 2025-03-02 11:08:30 --> <!-- Compression = gzip -->