From 746ad28545b3473cc17dd598dfcde2639edffca5 Mon Sep 17 00:00:00 2001 From: Erik Thuning <boooink@gmail.com> Date: Mon, 26 Feb 2024 13:10:13 +0100 Subject: [PATCH] Implemented internationalization. Let's see how many strings have been missed. --- config.php.example | 6 + html/en/base.html | 83 ++++ html/en/fragments.html | 843 +++++++++++++++++++++++++++++++++++ html/{ => sv}/base.html | 0 html/{ => sv}/fragments.html | 0 include/HistoryPage.php | 16 +- include/Page.php | 117 ++--- include/ProductPage.php | 16 +- include/Responder.php | 3 +- include/UserPage.php | 6 +- include/functions.php | 7 + include/translations.php | 232 ++++++++++ 12 files changed, 1251 insertions(+), 78 deletions(-) create mode 100644 html/en/base.html create mode 100644 html/en/fragments.html rename html/{ => sv}/base.html (100%) rename html/{ => sv}/fragments.html (100%) create mode 100644 include/translations.php diff --git a/config.php.example b/config.php.example index 82ee321..9f5aa15 100644 --- a/config.php.example +++ b/config.php.example @@ -6,6 +6,12 @@ $db_user = 'dbname'; $db_pass = 'dbpassword'; $db_name = 'dbuser'; +# Application language +$language = 'en'; + +# Site name +$name = 'My product tracker'; + # Email subject prefix # Will be prepended without change, so should probably end with a space $email_subject_prefix = "System name: "; diff --git a/html/en/base.html b/html/en/base.html new file mode 100644 index 0000000..0e1081f --- /dev/null +++ b/html/en/base.html @@ -0,0 +1,83 @@ +¤¤ head ¤¤ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon"> + <link rel="stylesheet" type="text/css" href="./template.css" /> + <link rel="stylesheet" type="text/css" href="./style.css" /> + <script type="text/javascript" src="./script.js"></script> + <link rel="stylesheet" type="text/css" href="./calendar/dhtmlxcalendar.css" /> + <script type="text/javascript" src="./calendar/dhtmlxcalendar.js"></script> + <title>¤title¤</title> + </head> + <body> + <div id="container"> + <a class="accessibility-link" + accesskey="s" + href="#contents" + title="Skip navigation"></a> + <div id="header"> + <a id="header-su-responsive" + href="https://www.su.se/english/" + title="To the Stockholm Universiy website"> + <img src="images/su_logo_responsive_en.png" + alt="Stockholm University" /> + </a> + + <a id="header-dsv" + href="https://dsv.su.se/english/" + title="To DSV's website" + accesskey="1"> + <img src="images/dsv_logo_en.png" + alt="Department of Computer and Systems Sciences" /> + </a> + + <a id="header-su" + href="https://www.su.se/english/" + title="To the Stockholm Universiy website"> + <img src="images/su_logo_en.gif" + alt="Stockholm University" /> + </a> + <div class="clear"> + </div> + </div> + <div id="menu"> + ¤menu¤ + <div class="clear"> + </div> + </div> + <div id="contents"> + ¤¤ foot ¤¤ + <div class="clear"> + </div> + </div> + + <div id="footer"> + <div id="footer-name"> + <div id="footer-dsv"> + Department of Computer and Systems Sciences + </div> + <div id="footer-su"> + Stockholm University + </div> + </div> + <div id="footer-contact"> + <a id="footer-contact-link" + href="https://www.su.se/department-of-computer-and-systems-sciences/about-the-department/contact" + accesskey="7"> + Contact + </a> + </div> + <div class="clear"> + </div> + </div> + </div> + </body> +</html> +¤¤ menuitem ¤¤ +<a class="item ¤align¤ ¤active¤" + href="?page=¤page¤"> + ¤title¤ +</a> diff --git a/html/en/fragments.html b/html/en/fragments.html new file mode 100644 index 0000000..78d2b3b --- /dev/null +++ b/html/en/fragments.html @@ -0,0 +1,843 @@ +¤¤ title ¤¤ +<h1>¤title¤</h1> + +¤¤ subtitle ¤¤ +<h2>¤title¤</h2> + +¤¤ item_link ¤¤ +<a href="./?page=¤page¤&action=show&id=¤id¤">¤name¤</a> + +¤¤ message ¤¤ +<div onClick="JavaScript:hideMessage()" + id="message" + class="¤type¤"> + ¤message¤ +</div> + +¤¤ user_table ¤¤ +<form id="newloan" + class="hidden" + method="GET"> + <input type="hidden" + name="action" + value="checkout" /> +</form> +<table id="user-table"> + <thead> + <tr> + <th> + Name + </th> + <th> + Username + </th> + <th> + Loan + </th> + <th> + </th> + </tr> + </thead> + <tbody> + ¤rows¤ + </tbody> +</table> + +¤¤ user_row ¤¤ +<tr> + <td> + ¤item_link¤<span title="¤notes¤">¤has_notes¤</span> + </td> + <td> + ¤name¤ + </td> + <td> + ¤loan¤ + </td> + <td> + <button form="newloan" + name="user" + value="¤name¤"> + New loan + </button> + </td> +</tr> + +¤¤ product_page ¤¤ +<div id="product-table"> + ¤product_table¤ +</div> + +¤¤ product_table ¤¤ +<table> + <thead> + <tr> + <th class="status"> + </th> + <th> + Name + </th> + <th> + Serial number + </th> + <th> + Status + </th> + </tr> + </thead> + <tbody class="¤type¤"> + ¤rows¤ + </tbody> +</table> + +¤¤ product_row ¤¤ +<tr> + <td class="status ¤status¤"> + </td> + <td> + ¤item_link¤ + </td> + <td> + ¤serial¤ + </td> + <td> + ¤note¤ + </td> +</tr> + +¤¤ product_detail_row ¤¤ +<tr> + <td class="status ¤status¤"> + </td> + <td colspan="3"> + <dl> + ¤details¤ + </dl> + </td> +</tr> + +¤¤ product_detail ¤¤ +<dt> + ¤name¤: +</dt> +<dd> + ¤value¤ +</dd> + +¤¤ template_management ¤¤ +<div> + <h2>Templates</h2> + <form class="dark templates" + onSubmit="JavaScript:loadTemplate(event)"> + <datalist id="template_suggest"></datalist> + <input type="hidden" + name="page" + value="new" /> + <input onFocus="JavaScript:suggest(this, 'template')" + list="template_suggest" + autocomplete="off" + type="text" + name="template" + value="¤template¤" + placeholder="Name" /> + <button> + Load + </button> + <button onClick="JavaScript:saveTemplate(event)"> + Save + </button> + <button onClick="JavaScript:deleteTemplate(event)"> + Delete + </button> + </form> +</div> + +¤¤ product_form ¤¤ +<div id="product-details"> + <h2>Product details</h2> + <form id="product-data" + onSubmit="JavaScript:saveProduct(event)" + class="data"> + <input type="hidden" + name="id" + value="¤id¤" /> + <datalist id="field_suggest"></datalist> + <datalist id="tag_suggest"></datalist> + <table> + <tfoot> + <tr> + <td> + </td> + <td> + <button id="save" + class="right"> + Save + </button> + <button id="reset" + class="right"> + Reset + </button> + </td> + </tr> + </tfoot> + <tbody> + <tr> + <td> + Name: + </td> + <td> + <input type="text" + name="name" + value="¤name¤" + onFocus="JavaScript:suggestContent(this)" + list="name_suggest" + autocomplete="off" /> + <datalist id="name_suggest"></datalist> + </td> + </tr> + <tr> + <td> + Manufacturer: + </td> + <td> + <input type="text" + name="brand" + value="¤brand¤" + onFocus="JavaScript:suggestContent(this)" + list="brand_suggest" + autocomplete="off" /> + <datalist id="brand_suggest"></datalist> + </td> + </tr> + <tr> + <td> + Invoice number: + </td> + <td> + <input type="text" + name="invoice" + value="¤invoice¤" /> + </td> + </tr> + <tr id="before_info"> + <td> + Serial number: + </td> + <td> + <input type="text" + name="serial" + value="¤serial¤" /> + </td> + </tr> + ¤info¤ + <tr> + <td> + <input onKeyPress="JavaScript:addField(event)" + onFocus="JavaScript:suggest(this, 'field')" + list="field_suggest" + autocomplete="off" + class="newfield" + type="text" + name="new_key" + placeholder="New field" /> + <button class="minibutton" + onClick="addField(event)"> + + + </button> + </td> + <td> + </td> + </tr> + <tr> + <td> + Tags: + </td> + <td id="tags"> + ¤tags¤ + <input onKeyPress="JavaScript:addTag(event)" + onFocus="JavaScript:suggest(this, 'tag')" + list="tag_suggest" + autocomplete="off" + class="newtag" + type="text" + name="new_tag" + placeholder="New tag" /> + <button class="minibutton" + onClick="JavaScript:addTag(event)"> + + + </button> + </td> + </tr> + </tbody> + </table> + </form> + <form id="product-actions" + class="¤hidden¤"> + <input type="hidden" + name="id" + value="¤id¤" /> + <button onClick="JavaScript:discardProduct(event)"> + Discard product + </button> + <button onClick="JavaScript:toggleService(event)"> + ¤service¤ + </button> + </form> +</div> +<div id="product-attachments" + class="¤hidden¤"> + <h2>Attachments</h2> + ¤attachments¤ + <form id="attachment_upload" + onSubmit="JavaScript:uploadAttachment(event)"> + <input type="hidden" + name="id" + value="¤id¤" /> + <input id="uploadfile" + name="uploadfile" + type="file" + onchange="showFile(event)"/> + <input id="filename" + name="filename" + type="text" + placeholder="Choose a file..." + onclick="selectFile(event)" + readonly /> + <button>Upload</button> + </form> +</div> + +¤¤ product_meta ¤¤ +<div id="product-history"> + <h2>Product history</h2> + ¤history¤ +</div> +<div id="product-label" + class="¤label_hidden¤"> + <h2>Label</h2> + ¤label¤ +</div> +<div id="product-direct-checkout" class="¤checkout_hidden¤"> + <h2>Check out</h2> + <form class="light" + onSubmit="JavaScript:checkoutProduct(event)"> + <datalist id="user_suggest"></datalist> + <input type="hidden" + name="page" + value="checkout" /> + <label for="user">Username:</label> + <input onFocus="JavaScript:suggest(this, 'user')" + type="text" + name="user" + list="user_suggest" + autocomplete="off" + placeholder="Username" + required /> + <input type="hidden" + id="product" + name="product" + value="¤serial¤" /> + <button> + Check out + </button> + <br/> + <label>Loan length:</label> + <button onClick="JavaScript:loanLength(event, 7, 'day')">1 week</button> + <button onClick="JavaScript:loanLength(event, 1, 'year')">1 year</button> + <button onClick="JavaScript:loanLength(event, 3, 'year')">3 years</button> + <br/> + <label for="end">End date:</label> + <input type="text" + id="end" + onClick="JavaScript:calendar(event)" + name="end" + value="¤end¤" /> + </form> +</div> + +¤¤ attachment_list ¤¤ +<ul class="attachment-list"> + ¤attachments¤ +</ul> + +¤¤ attachment ¤¤ +<li> + <strong>¤name¤</strong> (¤date¤): <a href="./?page=dl&id=¤id¤">Download</a> + <br/> + <form onSubmit="JavaScript:deleteAttachment(event)"> + <input type="hidden" + name="id" + value="¤id¤" /> + <input type="hidden" + name="name" + value="¤name¤" /> + <button>Delete</button> + </form> +</li> + +¤¤ product_label ¤¤ +<div class="qr"> + <a href="./?page=print&id=¤id¤" + title="Print"> + <span>¤name¤</span> + <img src="./?page=qr&id=¤id¤"> + <span>¤serial¤</span> + </a> +</div> + +¤¤ label_page ¤¤ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon"> + <link rel="stylesheet" type="text/css" href="./template.css" /> + <link rel="stylesheet" type="text/css" href="./style.css" /> + <title>¤title¤</title> + </head> + <body> + ¤label¤ + </body> +</html> + +¤¤ info_item ¤¤ +<tr> + <td> + ¤name¤: + </td> + <td> + <input type="text" + class="info_item" + name="¤key¤" + value="¤value¤" + onFocus="JavaScript:suggestContent(this)" + list="¤key¤_suggest" + autocomplete="off" /> + <datalist id="¤key¤_suggest"></datalist> + </td> +</tr> + +¤¤ tag ¤¤ +<p> + <span class="tag"> + <input type="hidden" + name="tag[]" + value="¤tag¤" /> + ¤tag¤ + <a class="tagremove" + onClick="JavaScript:removeTag(event)"> + x + </a> + </span> +</p> + +¤¤ user_details ¤¤ +<div id="user-details"> + <form onSubmit="JavaScript:updateUser(event)"> + <input type="hidden" + name="id" + value="¤id¤" /> + <table> + <tfoot> + <tr> + <td> + </td> + <td> + <button class="right" + id="save"> + Save + </button> + <button class="right" + id="reset"> + Reset + </button> + </td> + </tr> + </tfoot> + <tbody> + <tr> + <td> + Name: + </td> + <td> + <input type="text" + value="¤displayname¤" + disabled /> + </td> + </tr> + <tr> + <td> + Username: + </td> + <td> + <input type="text" + name="name" + value="¤name¤" /> + </td> + </tr> + <tr> + <td> + Notes: + </td> + <td> + <textarea name="notes">¤notes¤</textarea> + </td> + </tr> + </tbody> + </table> + </form> + <form method="GET"> + <input type="hidden" + name="action" + value="checkout" /> + <button name="user" + value="¤name¤"> + New loan + </button> + </form> +</div> +<div id="active-loans"> + <h2>Current loans</h2> + ¤active_loans¤ +</div> +<div id="inactive-loans"> + <h2>Old loans</h2> + ¤inactive_loans¤ +</div> + +¤¤ history_table ¤¤ +<table class="history"> + <thead> + <tr> + <th class="status"> + </th> + <th> + ¤item¤ + </th> + <th> + Start date + </th> + <th> + End date + </th> + <th> + Misc + </th> + </tr> + </thead> + <tbody> + ¤rows¤ + </tbody> +</table> + +¤¤ history_row ¤¤ +<tr> + <td class="status ¤status¤"> + </td> + <td> + ¤item_link¤ + </td> + <td> + ¤start_date¤ + </td> + <td> + ¤end_date¤ + </td> + <td> + ¤note¤ + </td> +</tr> + +¤¤ loan_extend_form ¤¤ +<button onClick="JavaScript:showExtend(event)"> + Extend +</button> +<form class="renew_confirm hidden" + onSubmit="JavaScript:extendLoan(event)"> + <input type="hidden" + name="product" + value="¤id¤" /> + <input onClick="JavaScript:calendar(event)" + id="¤id¤_date" + class="narrow" + type="text" + name="end" + value="¤end_new¤" /> + <button> + Save + </button> +</form> + + +¤¤ checkout_page ¤¤ +<div id="user-select"> + <h2>Choose borrower</h2> + <form class="dark" + action="./" + method="GET"> + <datalist id="user_suggest"></datalist> + <input type="hidden" + name="page" + value="checkout" /> + <div> + <label for="user"> + Username: + </label> + <input onFocus="JavaScript:suggest(this, 'user')" + type="text" + name="user" + id="user" + list="user_suggest" + autocomplete="off" + placeholder="Username" + value="¤user¤" /> + <button type="submit" > + Find + </button> + </div> + <div> + <label for="email"> + E-mail: + </label> + <input type="text" + name="email" + id="email" + autocomplete="off" + placeholder="E-mail" + value="¤email¤" /> + </div> + <div> + <label for="displayname"> + Name: + </label> + <input type="text" + name="displayname" + id="displayname" + value="¤displayname¤" + disabled /> + </div> + <div> + <label for="notes"> + Notes: + </label> + <textarea name="notes" + id="notes" + disabled>¤notes¤</textarea> + </div> + </form> +</div> +<div id="product-checkout"> + <h2>Check out product</h2> + <form class="light" + onSubmit="JavaScript:checkoutProduct(event)"> + <input type="hidden" + name="page" + value="checkout" /> + <input type="hidden" + name="user" + value="¤user¤"> + <label for="product">Product:</label> + <input type="text" + id="product" + name="product" + placeholder="Serial number" + required + ¤disabled¤ /> + <button> + Check out + </button> + <br/> + <label>Loan length:</label> + <button onClick="JavaScript:loanLength(event, 7, 'day')">1 week</button> + <button onClick="JavaScript:loanLength(event, 1, 'year')">1 year</button> + <button onClick="JavaScript:loanLength(event, 3, 'year')">3 years</button> + <br/> + <label for="end">End date:</label> + <input type="text" + id="end" + onClick="JavaScript:calendar(event)" + name="end" + value="¤end¤" + ¤disabled¤ /> + </form> + ¤subtitle¤ + ¤loan_table¤ +</div> + +¤¤ inventory_start ¤¤ +<form class="dark" + onSubmit="JavaScript:startInventory(event)"> + <button name="start"> + Start inventory + </button> +</form> + +¤¤ inventory_do ¤¤ + +<div id="inventory-overview" + class="dark"> + <span class="label"> + Start date: + </span> + ¤start_date¤ + <br/> + <span class="label"> + Total number of products: + </span> + ¤total_count¤ + <br/> + <span class="label"> + Number of registered products: + </span> + <span id="seen_count"> + ¤seen_count¤ + </span> + <form class="dark ¤hide¤" + onSubmit="JavaScript:endInventory(event)"> + <button name="end"> + End inventory + </button> + </form> +</div> +<form id="inventory-register" + class="light ¤hide¤" + onSubmit="JavaScript:inventoryProduct(event)"> + <label for="serial"> + Product: + </label> + <input type="text" + name="serial" + id="serial" + placeholder="Serial number" /> + <button> + Register + </button> +</form> +<div id="unseen-products"> + <h2>¤unseen_title¤</h2> + ¤unseen¤ +</div> +<div id="seen-products"> + <h2>Registered products</h2> + ¤seen¤ +</div> + +¤¤ return_page ¤¤ +<form class="dark" + onSubmit="JavaScript:returnProduct(event)"> + <label for="serial"> + Product: + </label> + <input type="text" + name="serial" + id="serial" + placeholder="Serial number" + required /> + <button> + Return + </button> +</form> + +¤¤ inventory_table ¤¤ +<table id="inventory-history"> + <thead> + <tr> + <th> + </th> + <th> + ¤item¤ + </th> + <th> + Start date + </th> + <th> + End date + </th> + <th> + Registered products + </th> + <th> + Missing products + </th> + </tr> + </thead> + <tbody> + ¤rows¤ + </tbody> +</table> + +¤¤ inventory_row ¤¤ +<tr> + <td> + </td> + <td> + ¤item_link¤ + </td> + <td> + ¤start_date¤ + </td> + <td> + ¤end_date¤ + </td> + <td> + ¤num_seen¤ + </td> + <td> + ¤num_unseen¤ + </td> +</tr> + +¤¤ search_form ¤¤ +<form onSubmit="JavaScript:doSearch(event)" + id="search" + class="dark"> + <p> + <label for="q">Search term:</label> + <input type="hidden" + name="page" + value="search" /> + <input type="text" + onKeyPress="JavaScript:searchInput(event)" + name="q" + id="q" + placeholder="What are you looking for?" + value="" + autofocus /> + <button type="submit"> + Search + </button> + </p> + <div id="terms"> + ¤terms¤ + </div> + <div class="clear"></div> +</form> +<p id="hints"> + Advanced searches are possible by prefixing a search term with a keyword ending with a colon, for example: <strong>serial:123456</strong> or <strong>tag:computer</tag> +</p> +<div id="found-products" + class="¤hidden¤"> + <h2>Products</h2> + ¤product_results¤ +</div> +<div id="found-users" + class="¤hidden¤"> + <h2>Users</h2> + ¤user_results¤ +</div> + +¤¤ search_term ¤¤ + +<p class="left"> + <span class="term"> + <input type="hidden" + name="¤key¤" + value="¤value¤" /> + ¤term¤ + <a class="termremove" + onClick="JavaScript:removeTerm(event)"> + x + </a> + </span> +</p> diff --git a/html/base.html b/html/sv/base.html similarity index 100% rename from html/base.html rename to html/sv/base.html diff --git a/html/fragments.html b/html/sv/fragments.html similarity index 100% rename from html/fragments.html rename to html/sv/fragments.html diff --git a/include/HistoryPage.php b/include/HistoryPage.php index 7d0f830..6761c2c 100644 --- a/include/HistoryPage.php +++ b/include/HistoryPage.php @@ -14,15 +14,15 @@ class HistoryPage extends Page { } catch(Exception $e) { $this->inventory = null; $this->action = 'list'; - $this->error = 'Det finns ingen inventering med det ID-numret.'; + $this->error = i18n('There is no inventory with that ID.'); } } switch($this->action) { case 'show': - $this->subtitle = 'Inventeringsdetaljer'; + $this->subtitle = i18n('Inventory details'); break; case 'list': - $this->subtitle = 'Historik'; + $this->subtitle = i18n('History'); break; } } @@ -30,16 +30,16 @@ class HistoryPage extends Page { protected function render_body() { switch($this->action) { case 'list': - print(replace(array('title' => 'Genomförda inventeringar'), + print(replace(array('title' => i18n('Past inventories')), $this->fragments['subtitle'])); print($this->build_inventory_table()); - print(replace(array('title' => 'Skrotade artiklar'), + print(replace(array('title' => i18n('Discarded products')), $this->fragments['subtitle'])); $discards = get_items('product_discarded'); if($discards) { print($this->build_product_table($discards)); } else { - print('Inga artiklar skrotade.'); + print(i18n('No products discarded.')); } break; case 'show': @@ -55,7 +55,7 @@ class HistoryPage extends Page { private function build_inventory_table() { $items = get_items('inventory_old'); if(!$items) { - return 'Inga inventeringar gjorda.'; + return i18n('No inventories have been performed.'); } $rows = ''; foreach($items as $inventory) { @@ -75,7 +75,7 @@ class HistoryPage extends Page { 'num_unseen' => $num_unseen), $this->fragments['inventory_row']); } - return replace(array('item' => 'Tillfälle', + return replace(array('item' => i18n('Number'), 'rows' => $rows), $this->fragments['inventory_table']); } diff --git a/include/Page.php b/include/Page.php index af01b01..e302222 100644 --- a/include/Page.php +++ b/include/Page.php @@ -3,22 +3,26 @@ abstract class Page extends Responder { protected abstract function render_body(); protected $page = 'checkout'; - protected $title = "DSV Utlåning"; + protected $title = ''; protected $subtitle = ''; protected $error = null; - protected $menuitems = array('checkout' => 'Låna', - 'return' => 'Lämna', - 'products' => 'Artiklar', - 'new' => 'Ny artikel', - 'users' => 'Låntagare', - 'inventory' => 'Inventera', - 'history' => 'Historik', - 'search' => 'Sök'); + protected $menuitems = array(); private $template_parts = array(); public function __construct() { + global $language, $name; parent::__construct(); - $this->template_parts = get_fragments('./html/base.html'); + + $this->title = $name; + $this->menuitems = array('checkout' => i18n('Check out'), + 'return' => i18n('Return'), + 'products' => i18n('Products'), + 'new' => i18n('New product'), + 'users' => i18n('Borrowers'), + 'inventory' => i18n('Inventory'), + 'history' => i18n('History'), + 'search' => i18n('Search')); + $this->template_parts = get_fragments("./html/$language/base.html"); if(isset($_GET['page'])) { $this->page = $_GET['page']; @@ -118,7 +122,7 @@ abstract class Page extends Responder { $loan_str = $product->get_name(); break; default: - $loan_str = $count .' artiklar'; + $loan_str = i18n('{count} products', $count); break; } $replacements['loan'] = $loan_str; @@ -143,35 +147,31 @@ abstract class Page extends Responder { 'name' => $product->get_name(), 'page' => 'products'), $this->fragments['item_link']); - $note = 'Tillgänglig'; + $note = i18n('Available'); $status = $product->get_status(); switch($status) { - case 'discarded': - $discarded = format_date($product->get_discardtime()); - $note = 'Skrotad '.$discarded; - break; - case 'service': - $service = $product->get_active_service(); - $note = 'På service sedan ' - .format_date($service->get_starttime()); - break; - case 'on_loan': - case 'overdue': - $loan = $product->get_active_loan(); - $user = $loan->get_user(); - $replacements = array('name' => $user->get_displayname($this->ldap), - 'id' => $user->get_id(), - 'page' => 'users'); - $userlink = replace($replacements, - $this->fragments['item_link']); - $note = 'Utlånad till '.$userlink; - if($loan->is_overdue()) { - $note .= ', försenad'; - } else { - $note .= ', slutdatum ' - .format_date($loan->get_endtime()); - } - break; + case 'discarded': + $discarded = format_date($product->get_discardtime()); + $note = i18n('Discarded on {date}', $discarded); + break; + case 'service': + $service = $product->get_active_service(); + $note = i18n('Being serviced since {date}', + $service->get_starttime()); + break; + case 'on_loan': + case 'overdue': + $loan = $product->get_active_loan(); + $user = $loan->get_user(); + $replacements = array( + 'name' => $user->get_displayname($this->ldap), + 'id' => $user->get_id(), + 'page' => 'users' + ); + $userlink = replace($replacements, + $this->fragments['item_link']); + $note = i18n('Borrowed by {user} {loan}', $userlink, $loan); + break; } $out = replace(array('status' => $status, 'item_link' => $prodlink, @@ -229,7 +229,7 @@ abstract class Page extends Responder { $this->fragments['history_row']); } return replace(array('rows' => $rows, - 'item' => 'Artikel'), + 'item' => i18n('Product')), $this->fragments['history_table']); } @@ -249,34 +249,35 @@ abstract class Page extends Responder { $discardtime = $product->get_discardtime(); if($discardtime && $discardtime < $regtime) { $status = 'discarded'; - $note = 'Skrotad '.format_date($discardtime); + $note = i18n('Discarded on {date}', + format_date($discardtime)); } else { $status = 'available'; } } else if($event instanceof Service) { $status = 'service'; - $note = 'På service sedan '.format_date($event->get_starttime()); + $starttime = $event->get_starttime(); $returntime = $event->get_returntime(); + $note = i18n('Being serviced since {date}', $starttime); if($returntime) { - $note .= ', åter den '.format_date($returntime); + $note = i18n("Serviced between {start} and {end}", + $starttime, + $returntime); } } else if($event instanceof Loan) { $user = $event->get_user(); - $userlink = replace(array('name' => $user->get_displayname($this->ldap), - 'id' => $user->get_id(), - 'page' => 'users'), - $this->fragments['item_link']); + $userlink = replace( + array('name' => $user->get_displayname($this->ldap), + 'id' => $user->get_id(), + 'page' => 'users'), + $this->fragments['item_link']); $status = 'on_loan'; - $note = 'Utlånad till '.$userlink; - $returntime = $event->get_returntime(); + $note = i18n('Borrowed by {user} {loan} {inventorytime}', + $userlink, + $event, + $regtime); if($event->get_endtime() < $regtime) { $status = 'overdue'; - $note .= ', försenad'; - } else { - $note .= ', slutdatum '.format_date($event->get_endtime()); - } - if($returntime) { - $note .= ', återlämnad '.format_date($returntime); } } $rows .= replace(array('status' => $status, @@ -302,17 +303,17 @@ abstract class Page extends Responder { $unseen[] = $product; } } - $missing = 'Saknade artiklar'; + $missing = i18n('Missing products'); $hidden = 'hidden'; if($interactive) { - $missing = 'Kvarvarande artiklar'; + $missing = i18n('Remaining products'); $hidden = ''; } - $unseen_table = 'Inga artiklar saknas.'; + $unseen_table = i18n('No products are missing.'); if($unseen) { $unseen_table = $this->build_product_table($unseen); } - $seen_table = 'Inga artiklar inventerade.'; + $seen_table = i18n('No products registered'); if($seen) { $seen_table = $this->build_seen_table($seen, $inventory); } diff --git a/include/ProductPage.php b/include/ProductPage.php index 8f00fa2..4edd51d 100644 --- a/include/ProductPage.php +++ b/include/ProductPage.php @@ -16,16 +16,16 @@ class ProductPage extends Page { } catch(Exception $e) { $this->action = 'list'; $this->product = null; - $this->error = 'Det finns ingen artikel med det ID-numret.'; + $this->error = i18n('There is no product with that ID.'); } } } switch($this->action) { case 'show': - $this->subtitle = 'Artikeldetaljer'; + $this->subtitle = i18n('Product details'); break; case 'list': - $this->subtitle = 'Artikellista'; + $this->subtitle = i18n('Product list'); break; } } @@ -70,7 +70,7 @@ class ProductPage extends Page { 'label_hidden' => 'hidden', 'checkout_hidden' => 'hidden', 'hidden' => '', - 'service' => 'Starta service', + 'service' => i18n('Start service'), 'history' => $history, 'attachments' => $attachments, 'end' => format_date(default_loan_end(time()))); @@ -81,7 +81,7 @@ class ProductPage extends Page { if(!$this->product->get_discardtime()) { $fields['label_hidden'] = ''; if($this->product->get_status() == 'service') { - $fields['service'] = 'Avsluta service'; + $fields['service'] = i18n('End service'); } if($this->product->get_status() == 'available') { $fields['checkout_hidden'] = ''; @@ -94,7 +94,7 @@ class ProductPage extends Page { private function build_history_table($history) { if(!$history) { - return 'Ingen historik att visa.'; + return i18n('No history to display.'); } $rows = ''; foreach($history as $event) { @@ -126,14 +126,14 @@ class ProductPage extends Page { $this->fragments['history_row']); } return replace(array('rows' => $rows, - 'item' => 'Låntagare'), + 'item' => i18n('Borrower')), $this->fragments['history_table']); } private function build_attachment_list($attachments) { if(!$attachments) { - return '<p>Inga bilagor.</p>'; + return '<p>'.i18n('No attachments.').'</p>'; } $items = ''; foreach($attachments as $attachment) { diff --git a/include/Responder.php b/include/Responder.php index ff621cd..69864e0 100644 --- a/include/Responder.php +++ b/include/Responder.php @@ -4,7 +4,8 @@ abstract class Responder { protected $ldap = null; public function __construct() { - $this->fragments = get_fragments('./html/fragments.html'); + global $language; + $this->fragments = get_fragments("./html/$language/fragments.html"); $this->ldap = new Ldap(); } diff --git a/include/UserPage.php b/include/UserPage.php index c5ff4e1..4473aa7 100644 --- a/include/UserPage.php +++ b/include/UserPage.php @@ -16,16 +16,16 @@ class UserPage extends Page { } catch(Exception $e) { $this->user = null; $this->action = 'list'; - $this->error = 'Det finns ingen användare med det ID-numret.'; + $this->error = i18n('There is no user with that ID.'); } } } switch($this->action) { case 'show': - $this->subtitle = 'Låntagardetaljer'; + $this->subtitle = i18n('Borrower details'); break; case 'list': - $this->subtitle = 'Låntagarlista'; + $this->subtitle = i18n('Borrower list'); break; } } diff --git a/include/functions.php b/include/functions.php index f265439..84d4a4c 100644 --- a/include/functions.php +++ b/include/functions.php @@ -1,5 +1,7 @@ <?php +require_once('./include/translations.php'); + /* Takes an html file containing named fragments. Returns an associative array on the format array[name]=>fragment. @@ -43,6 +45,11 @@ function get_fragments($infile) { return try_adding($name, $current_fragment, $out, $infile); } +function i18n($string, ...$args) { + global $language, $i18n; + return $i18n[$string][$language](...$args); +} + function try_adding($key, $value, $array, $filename) { if(array_key_exists($key, $array)) { throw new Exception('There is already a fragment with that name in '.$filename); diff --git a/include/translations.php b/include/translations.php new file mode 100644 index 0000000..7ed6dd7 --- /dev/null +++ b/include/translations.php @@ -0,0 +1,232 @@ +<?php + +$i18n = array( + "Check out" => array( + "en" => function() { return "Check out"; }, + "sv" => function() { return "Låna"; }, + ), + "Return" => array( + "en" => function() { return "Return"; }, + "sv" => function() { return "Lämna"; }, + ), + "Products" => array( + "en" => function() { return "Products"; }, + "sv" => function() { return "Artiklar"; }, + ), + "Product" => array( + "en" => function() { return "Product"; }, + "sv" => function() { return "Artikel"; }, + ), + "New product" => array( + "en" => function() { return "New product"; }, + "sv" => function() { return "Ny artikel"; }, + ), + "Borrowers" => array( + "en" => function() { return "Borrowers"; }, + "sv" => function() { return "Låntagare"; }, + ), + "Borrower" => array( + "en" => function() { return "Borrower"; }, + "sv" => function() { return "Låntagare"; }, + ), + "Inventory" => array( + "en" => function() { return "Inventory"; }, + "sv" => function() { return "Inventera"; }, + ), + "History" => array( + "en" => function() { return "History"; }, + "sv" => function() { return "Historik"; }, + ), + "Search" => array( + "en" => function() { return "Search"; }, + "sv" => function() { return "Sök"; }, + ), + "{count} products" => array( + "en" => function($count) { return "$count products"; }, + "sv" => function($count) { return "$count artiklar"; }, + ), + "Available" => array( + "en" => function() { return "Available"; }, + "sv" => function() { return "Tillgänglig"; }, + ), + "Discarded on {date}" => array( + "en" => function($date) { return "Discarded on $date"; }, + "sv" => function($date) { return "Skrotad $date"; }, + ), + "Being serviced since {date}" => array( + "en" => function($date) { + $date = format_date($date); + return "Being serviced since $date"; + }, + "sv" => function($date) { + $date = format_date($date); + return "På service sedan $date"; + }, + ), + "Serviced between {start} and {end}" => array( + "en" => function($start, $end) { + $start = format_date($start); + $end = format_date($end); + return "Serviced between $start and $end"; + }, + "sv" => function($start, $end) { + $start = format_date($start); + $end = format_date($end); + return "På service mellan $start och $end"; + }, + ), + "Borrowed by {user} {loan}" => array( + "en" => function($user, $loan) { + $note = "Borrowed by $user"; + if($loan->is_overdue()) { + $note .= ', overdue'; + } else { + $note .= ', end date '.format_date($loan->get_endtime()); + } + return $note; + }, + "sv" => function($user, $loan) { + $note = "Utlånad till $user"; + if($loan->is_overdue()) { + $note .= ', försenad'; + } else { + $note .= ', slutdatum '.format_date($loan->get_endtime()); + } + return $note; + }, + ), + "Borrowed by {user} {loan} {inventorytime}" => array( + "en" => function($user, $loan, $inventorytime) { + $note = "Borrowed by $user"; + $endtime = $loan->get_endtime(); + $returntime = $loan->get_returntime(); + if($endtime < $inventorytime) { + $note .= ', overdue'; + } else { + $note .= ', end date '.format_date($endtime); + } + if($returntime) { + $note .= ', returned on '.format_date($returntime); + } + return $note; + }, + "sv" => function($user, $loan, $inventorytime) { + $note = "Utlånad till $user"; + $endtime = $loan->get_endtime(); + $returntime = $loan->get_returntime(); + if($endtime < $inventorytime) { + $note .= ', försenad'; + } else { + $note .= ', slutdatum '.format_date($endtime); + } + if($returntime) { + $note .= ', återlämnad '.format_date($returntime); + } + return $note; + }, + ), + "Missing products" => array( + "en" => function() { return "Missing products"; }, + "sv" => function() { return "Saknade artiklar"; }, + ), + "Remaining products" => array( + "en" => function() { return "Remaining products"; }, + "sv" => function() { return "Kvarvarande artiklar"; }, + ), + "No products are missing." => array( + "en" => function() { return "No products are missing."; }, + "sv" => function() { return "Inga artiklar saknas."; }, + ), + "No products registered" => array( + "en" => function() { return "No products registered"; }, + "sv" => function() { return "Inga artiklar inventerade."; }, + ), + "There is no product with that ID." => array( + "en" => function() { + return "There is no product with that ID."; + }, + "sv" => function() { + return "Det finns ingen artikel med det ID-numret."; + }, + ), + "Product details" => array( + "en" => function() { return "Product details"; }, + "sv" => function() { return "Artikeldetaljer"; }, + ), + "Product list" => array( + "en" => function() { return "Product list"; }, + "sv" => function() { return "Artikellista"; }, + ), + "Start service" => array( + "en" => function() { return "Start service"; }, + "sv" => function() { return "Starta service"; }, + ), + "End service" => array( + "en" => function() { return "End service"; }, + "sv" => function() { return "Avsluta service"; }, + ), + "No history to display." => array( + "en" => function() { return "No history to display."; }, + "sv" => function() { return "Ingen historik att visa."; }, + ), + "No attachments." => array( + "en" => function() { return "No attachments."; }, + "sv" => function() { return "Inga bilagor."; }, + ), + "Inventory details" => array( + "en" => function() { return "Inventory details"; }, + "sv" => function() { return "Inventeringsdetaljer"; }, + ), + "There is no inventory with that ID." => array( + "en" => function() { + return "There is no inventory with that ID."; + }, + "sv" => function() { + return "Det finns ingen inventering med det ID-numret."; + }, + ), + "Past inventories" => array( + "en" => function() { return "Past inventories"; }, + "sv" => function() { return "Genomförda inventeringar"; }, + ), + "Discarded products" => array( + "en" => function() { return "Discarded products"; }, + "sv" => function() { return "Skrotade artiklar"; }, + ), + "No products discarded." => array( + "en" => function() { return "No products discarded."; }, + "sv" => function() { return "Inga artiklar skrotade."; }, + ), + "No inventories have been performed." => array( + "en" => function() { return "No inventories have been performed."; }, + "sv" => function() { return "Inga inventeringar gjorda."; }, + ), + "Number" => array( + "en" => function() { return "Number"; }, + "sv" => function() { return "Nummer"; }, + ), + "There is no user with that ID." => array( + "en" => function() { + return "There is no user with that ID."; + }, + "sv" => function() { + return "Det finns ingen användare med det ID-numret."; + }, + ), + "Borrower list" => array( + "en" => function() { return "Borrower list"; }, + "sv" => function() { return "Låntagarlista"; }, + ), + "Borrower details" => array( + "en" => function() { return "Borrower details"; }, + "sv" => function() { return "Låntagardetaljer"; }, + ), + /* + "" => array( + "en" => function() { return ""; }, + "sv" => function() { return ""; }, + ), + */ +); + +?>