<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrew Owen</title>
    <description>The latest articles on DEV Community by Andrew Owen (@aowendev).</description>
    <link>https://dev.to/aowendev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F857313%2F5abec2ec-6b59-4760-8a8e-b1916f7cc537.jpg</url>
      <title>DEV Community: Andrew Owen</title>
      <link>https://dev.to/aowendev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aowendev"/>
    <language>en</language>
    <item>
      <title>Creating an API style guide</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Sun, 06 Apr 2025 12:55:30 +0000</pubDate>
      <link>https://dev.to/aowendev/creating-an-api-style-guide-5m8</link>
      <guid>https://dev.to/aowendev/creating-an-api-style-guide-5m8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I was recently asked for my favorite resources and best practices for writing clear and structured API docs. I've developed my own style for writing API docs, but up until now I haven't published it. Although I've mainly worked with REST APIs, this guidance applies equally to GraphQL and any other APIs. But before I get to writing style, the most important requirement for good API docs is a good API. If you're using REST, validate it with &lt;a href="https://stoplight.io/" rel="noopener noreferrer"&gt;Stoplight&lt;/a&gt;. If endpoints are inconsistent in how they handle common parameters, there's no way to write around the problem. This seems to be more of an issue with REST and may account for the move away from REST toward GraphQL. The next thing that you need is static, searchable docs. Don't expect your GraphQL users to find the information they need by browsing your schema in Apollo. Don't expect your REST users to scroll to the bottom of the Swagger UI page to find out how to format data for a given endpoint. If you need a no-budget solution, &lt;a href="https://redocly.com/docs/cli" rel="noopener noreferrer"&gt;Redocly CLI&lt;/a&gt; and &lt;a href="https://github.com/magidoc-org/magidoc" rel="noopener noreferrer"&gt;Magidoc&lt;/a&gt; are good places to start for REST and GraphQL respectively. Unless you use AWS hosting (which doesn't play nicely with Magidoc's clean URLs). And don't think that you're done when you've published your schema. Developers need workflows, code examples and reference information to understand how they are expected to use your API. Ideally, this information should live in a public developer portal. Your rivals are not going to be able to clone your product by examining your API. And even if they start adding features based on it, you'll always be several steps ahead.&lt;/p&gt;

&lt;h3&gt;
  
  
  General style guidelines
&lt;/h3&gt;

&lt;p&gt;When I became an API writer I found that, just like other software, docs are often an afterthought. But if you want people to use your APIs, they need to be well documented. However, API writing isn’t the same as traditional technical writing. Use a terse, factual writing style. Sentence fragments are desirable. Avoid adjectives and adverbs. You need to provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete information about each API component.&lt;/li&gt;
&lt;li&gt;Contact details in case developers have questions or require additional assistance.&lt;/li&gt;
&lt;li&gt;Flow charts showing the sequence of the most commonly used methods for common use cases.&lt;/li&gt;
&lt;li&gt;Getting Started guides showing how to develop a program for a common use cases.&lt;/li&gt;
&lt;li&gt;Performance and tuning information.&lt;/li&gt;
&lt;li&gt;Sample programs demonstrating common use cases.&lt;/li&gt;
&lt;li&gt;Working code snippets for each method, function, and resource. You don’t need complete examples, but show a common use of that element.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unless you are writing exclusively for an internal audience, use American English and don't translate your API schema. The language should be simple enough for a developer to follow without requiring translation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Endpoint names
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;lower case (except initialisms and acronyms)&lt;/li&gt;
&lt;li&gt;descriptive (action—objects—criteria)&lt;/li&gt;
&lt;li&gt;as brief as possible&lt;/li&gt;
&lt;li&gt;active voice&lt;/li&gt;
&lt;li&gt;use CRUD for the action (&lt;code&gt;create&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;include all match criteria (example: &lt;code&gt;by user or ID&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: read API key by ID&lt;/p&gt;

&lt;h4&gt;
  
  
  Endpoint descriptions
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sentence case.&lt;/li&gt;
&lt;li&gt;descriptive (action—objects—criteria)&lt;/li&gt;
&lt;li&gt;brief sentences&lt;/li&gt;
&lt;li&gt;passive voice&lt;/li&gt;
&lt;li&gt;human readable actions. Example:

&lt;ul&gt;
&lt;li&gt;POST: Creates&lt;/li&gt;
&lt;li&gt;GET: Retrieves&lt;/li&gt;
&lt;li&gt;PUT: Updates&lt;/li&gt;
&lt;li&gt;DELETE: Removes&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: Retrieves details of a registered API key for the current user by ID.&lt;/p&gt;

&lt;h4&gt;
  
  
  Parameters
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;human readable&lt;/li&gt;
&lt;li&gt;brief&lt;/li&gt;
&lt;li&gt;provide examples&lt;/li&gt;
&lt;li&gt;link to relevant standards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: expiry: Expiry date (&lt;a href="https://en.wikipedia.org/wiki/ISO_8601" rel="noopener noreferrer"&gt;ISO 8601&lt;/a&gt; format)&lt;/p&gt;

&lt;h4&gt;
  
  
  Responses
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;list all expected responses&lt;/li&gt;
&lt;li&gt;for REST provide at least the minimum boilerplate text for each response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: &lt;strong&gt;404&lt;/strong&gt; Not found: The URI isn’t valid, or the requested resource doesn’t exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  REST specific guidelines
&lt;/h3&gt;

&lt;p&gt;I've documented the principles of REST APIs elsewhere. No-one follows them, which is why APIs are described as RESTful. HTTP methods should be used consistently for specific tasks.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;CRUD&lt;/th&gt;
&lt;th&gt;Typical response code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Create&lt;/td&gt;
&lt;td&gt;201 Created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;200 Success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;Update&lt;/td&gt;
&lt;td&gt;202 Accepted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;Delete&lt;/td&gt;
&lt;td&gt;204 No Content&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The other HTTP methods are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HEAD — Almost identical to GET, but without the response body.&lt;/li&gt;
&lt;li&gt;OPTIONS — Describes the communication options for the target resource.&lt;/li&gt;
&lt;li&gt;PATCH — Applies partial modifications to a resource.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would recommend avoiding these three in public APIs, although you may have reasons to use them internally.&lt;/p&gt;

&lt;h4&gt;
  
  
  Boilerplate text
&lt;/h4&gt;

&lt;p&gt;One of the limitations of the current generation of Markdown and REST API doc tools is that they have little to no support for automated content reuse. For that reason, you should keep a list of standard definitions and valid payload examples. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Address — Use your company’s main address.&lt;/li&gt;
&lt;li&gt;Barcode — &lt;code&gt;019000000002&lt;/code&gt; (UPC-A format including check digit)&lt;/li&gt;
&lt;li&gt;Brand — &lt;code&gt;Ownbrand&lt;/code&gt; (too generic to be registered as a trademark)&lt;/li&gt;
&lt;li&gt;Country — &lt;code&gt;USA&lt;/code&gt; (ISO 3166-1 alpha-3 format)&lt;/li&gt;
&lt;li&gt;Credit card number — &lt;code&gt;4111111111111111&lt;/code&gt; (Use any CVV, NAME and future EXPIRY DATE. Keep purchase value under $500. &lt;a href="https://docs.stripe.com/testing" rel="noopener noreferrer"&gt;https://docs.stripe.com/testing&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Currency — &lt;code&gt;USD&lt;/code&gt; (&lt;a href="https://www.iso.org/iso-4217-currency-codes.html" rel="noopener noreferrer"&gt;https://www.iso.org/iso-4217-currency-codes.html&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Date time offset — &lt;code&gt;2019-11-01T00:00:00-05:00&lt;/code&gt; (&lt;a href="https://en.wikipedia.org/wiki/ISO_8601" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/ISO_8601&lt;/a&gt;, use long version to avoid ambiguity)&lt;/li&gt;
&lt;li&gt;Debit card number — &lt;code&gt;6304000000000000&lt;/code&gt; (bank card number with the defunct Laser identifier)&lt;/li&gt;
&lt;li&gt;Email — &lt;a href="//mailto:username@example.com"&gt;username@example.com&lt;/a&gt; (example.com is reserved for examples)&lt;/li&gt;
&lt;li&gt;First name — Don’t use other terms for first name. In some cultures, the family name precedes the given name.&lt;/li&gt;
&lt;li&gt;GUID — &lt;code&gt;01234567-890a-bcde-f012-34567890abcd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Last name — Don’t use other terms for last name. In some cultures, the family name precedes the given name.&lt;/li&gt;
&lt;li&gt;Telephone — &lt;code&gt;+1 202 555 0199&lt;/code&gt; (555 numbers ending 0100-0199 are fictitious)&lt;/li&gt;
&lt;li&gt;URL — &lt;a href="https://www.example.com" rel="noopener noreferrer"&gt;www.example.com&lt;/a&gt; (example.com is reserved for documentation)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  HTTP response codes
&lt;/h4&gt;

&lt;p&gt;You should provide as much response code information as possible. The minimum acceptable level of documentation is the number, a meaningful short description and an explanation for typical use cases.&lt;/p&gt;

&lt;h5&gt;
  
  
  1xx: information
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;100 — Continue.&lt;/li&gt;
&lt;li&gt;101 — Switching protocols.&lt;/li&gt;
&lt;li&gt;102 — Processing.&lt;/li&gt;
&lt;li&gt;103 — Early hints.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  2xx: success
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;200 — Success: Object created or fetched.&lt;/li&gt;
&lt;li&gt;201 — Created: Object created or replaced.&lt;/li&gt;
&lt;li&gt;202 — Accepted: Processing asynchronous request.&lt;/li&gt;
&lt;li&gt;203 — Non-authoritative Information.&lt;/li&gt;
&lt;li&gt;204 — No content.&lt;/li&gt;
&lt;li&gt;205 — Reset content.&lt;/li&gt;
&lt;li&gt;206 — Partial content.&lt;/li&gt;
&lt;li&gt;207 — Multi-status.&lt;/li&gt;
&lt;li&gt;208 — Already reported.&lt;/li&gt;
&lt;li&gt;226 — Instance-manipulations used.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  3xx: redirection
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;300 — Multiple choices.&lt;/li&gt;
&lt;li&gt;301 — Moved permanently.&lt;/li&gt;
&lt;li&gt;302 — Found.&lt;/li&gt;
&lt;li&gt;303 — See other.&lt;/li&gt;
&lt;li&gt;304 — Not modified.&lt;/li&gt;
&lt;li&gt;305 — Use proxy.&lt;/li&gt;
&lt;li&gt;306 — Switch proxy.&lt;/li&gt;
&lt;li&gt;307 — Temporary redirect.&lt;/li&gt;
&lt;li&gt;308 — Permanent redirect.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  4xx: client errors
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;400 — Bad request: The request wasn’t valid or can’t be otherwise
served. Typical when there is a syntax error in the request. Further
details of the error are provided in the response payload body.&lt;/li&gt;
&lt;li&gt;401 — Unauthorized: Missing or incorrect authentication credentials.&lt;/li&gt;
&lt;li&gt;402 — Payment required.&lt;/li&gt;
&lt;li&gt;403 — Forbidden: The request is understood, but it was refused or
access was denied. The response payload body provides further details of
the error.&lt;/li&gt;
&lt;li&gt;404 — Not found: The URI isn’t valid, or the requested resource doesn’t exist.&lt;/li&gt;
&lt;li&gt;405 — Method not allowed: The requested resource doesn’t support the method.&lt;/li&gt;
&lt;li&gt;406 — Not acceptable.&lt;/li&gt;
&lt;li&gt;407 — Proxy authentication required.&lt;/li&gt;
&lt;li&gt;408 — Request timeout.&lt;/li&gt;
&lt;li&gt;409 — Conflict.&lt;/li&gt;
&lt;li&gt;410 — Gone.&lt;/li&gt;
&lt;li&gt;411 — Length required.&lt;/li&gt;
&lt;li&gt;412 — Precondition failed.&lt;/li&gt;
&lt;li&gt;413 — Payload too large: The request is larger than the server is able to process.&lt;/li&gt;
&lt;li&gt;414 — URI too long.&lt;/li&gt;
&lt;li&gt;415 — Unsupported media type.&lt;/li&gt;
&lt;li&gt;416 — Range can’t be satisfied.&lt;/li&gt;
&lt;li&gt;417 — Expectation failed.&lt;/li&gt;
&lt;li&gt;418 — I’m a teapot (don’t ask).&lt;/li&gt;
&lt;li&gt;421 — Misdirected request.&lt;/li&gt;
&lt;li&gt;422 — Entity can’t be processed: The request was well-formed, but the data couldn’t be processed due to semantic errors.&lt;/li&gt;
&lt;li&gt;423 — Locked.&lt;/li&gt;
&lt;li&gt;424 — Failed dependency.&lt;/li&gt;
&lt;li&gt;425 — Too early.&lt;/li&gt;
&lt;li&gt;426 — Upgrade required.&lt;/li&gt;
&lt;li&gt;428 — Precondition required.&lt;/li&gt;
&lt;li&gt;429 — Too many requests.&lt;/li&gt;
&lt;li&gt;431 — Request header fields too large.&lt;/li&gt;
&lt;li&gt;451 — Unavailable for legal reasons.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  5xx: server errors
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;500 — Internal server error: This is usually a temporary error, for
example in a high load situation or if an endpoint is temporarily having
issues.&lt;/li&gt;
&lt;li&gt;501 — Not implemented.&lt;/li&gt;
&lt;li&gt;502 — Bad gateway.&lt;/li&gt;
&lt;li&gt;503 — Service unavailable.&lt;/li&gt;
&lt;li&gt;504 — Gateway timeout.&lt;/li&gt;
&lt;li&gt;505 — HTTP version not supported.&lt;/li&gt;
&lt;li&gt;506 — Variant also negotiates.&lt;/li&gt;
&lt;li&gt;507 — Insufficient storage.&lt;/li&gt;
&lt;li&gt;508 — Loop detected.&lt;/li&gt;
&lt;li&gt;510 — Not extended.&lt;/li&gt;
&lt;li&gt;511 — Network authentication required.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>graphql</category>
      <category>rest</category>
      <category>api</category>
      <category>writing</category>
    </item>
    <item>
      <title>Getting started with GraphQL</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Sun, 06 Apr 2025 12:53:05 +0000</pubDate>
      <link>https://dev.to/aowendev/getting-started-with-graphql-44g</link>
      <guid>https://dev.to/aowendev/getting-started-with-graphql-44g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GraphQL is an API query and manipulation language. Created by Facebook in 2012, it was open-sourced in 2015. In 2018 it moved to the GraphQL Foundation and introduced a schema definition language (SDL). It seems to be replacing REST as the standard way to expose public APIs. With REST, you have to define the inputs and outputs for each endpoint. Whereas with GraphQL, there's only one endpoint and you define a schema. The user sends only the required data to get what they want back. And unlike SQL, you're not limited to a single data source.&lt;/p&gt;

&lt;p&gt;This year, I've had to get rapidly up-to-speed with GraphQL. I thought I'd be starting from nothing, but I'd forgotten that TinaCMS (the headless content management system that I use with this site) uses it. One of the first problems I had to solve was how to generate static documentation. My limited research led me to two possible solutions: &lt;a href="https://github.com/anvilco/spectaql" rel="noopener noreferrer"&gt;SpectaQL&lt;/a&gt;, developed from the earlier &lt;a href="https://github.com/wayfair/dociql" rel="noopener noreferrer"&gt;DociQL&lt;/a&gt;, and &lt;a href="https://github.com/magidoc-org/magidoc" rel="noopener noreferrer"&gt;Magidoc&lt;/a&gt;. The latter has built-in search, so that made my choice for me. I use Hugo as my static site generator, so the first thing I had to do was start the local version of TinaCMS from my site's Git repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tinacms dev - c &lt;span class="s2"&gt;"hugo server -D -p 1313"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the local server running, you can access &lt;a href="https://github.com/graphql/graphiql" rel="noopener noreferrer"&gt;GraphiQL&lt;/a&gt; at &lt;a href="http://localhost:1313/admin/#/graphql" rel="noopener noreferrer"&gt;http://localhost:1313/admin/#/graphql&lt;/a&gt;. GraphiQL is a reference implementation of the GraphQL API playground. If it's too basic for you, there's a commercial alternative called &lt;a href="https://www.apollographql.com/" rel="noopener noreferrer"&gt;Apollo&lt;/a&gt;. The TinaCMS implementation gives you three options (selected from the icons on the left):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docs&lt;/strong&gt;: API docs in a tree structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;History&lt;/strong&gt;: Previous queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queries&lt;/strong&gt;: Query builder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After you've taken a look at the Docs section in GraphQL, you'll understand why I wanted to generate a static site. With Magidoc it's easy. I added this configuration file to my Git repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;introspection:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'url'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;url:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'http://localhost:&lt;/span&gt;&lt;span class="mi"&gt;4001&lt;/span&gt;&lt;span class="err"&gt;/graphql'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;website:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;template:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'carbon-multi-page'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;customStyles:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'/static/css/custom.css'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then to generate the documentation, enter: &lt;code&gt;pnpm add --global @magidoc/cli\@latest &amp;amp;&amp;amp; magidoc generate&lt;/code&gt; (you'll need pnpm installed). By default, this creates a static site in a folder called docs. However, you can't just open the index.html file. You'll need to launch the server with &lt;code&gt;magidoc preview&lt;/code&gt; and then follow the link. You may want to add the docs folder to your &lt;code&gt;.gitignore&lt;/code&gt; file. But in production, you can deploy using any &lt;a href="https://magidoc.js.org/deployment/others" rel="noopener noreferrer"&gt;web server&lt;/a&gt;. The output is based on IBM's &lt;a href="https://carbondesignsystem.com/" rel="noopener noreferrer"&gt;Carbon Design System&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now you've got some static searchable documentation, you can start exploring your schema. GraphQL schema can be written in any programming language that implements the type system. With TinaCMS that means either TypeScript or JavaScript. Here's a snippet of my Tina &lt;code&gt;config.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;schema:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;collections:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="err"&gt;format:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Blog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="err"&gt;path:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content/blog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="err"&gt;defaultItem:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;draft:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="err"&gt;fields:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"draft"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Draft"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;required:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;isTitle:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;required:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"datetime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image_license"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Image License"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'tags'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Tags'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;list:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'rich-text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;isBody:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Body"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;templates:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'shortcode'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'shortcode'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a schema type called &lt;code&gt;Blog&lt;/code&gt; with the fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;draft&lt;/strong&gt;: &lt;code&gt;Boolean!&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;title&lt;/strong&gt;: &lt;code&gt;String!&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;date&lt;/strong&gt;: &lt;code&gt;String&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;description&lt;/strong&gt;: &lt;code&gt;String&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;image&lt;/strong&gt;: &lt;code&gt;String&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;image_license&lt;/strong&gt;: &lt;code&gt;String&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tags&lt;/strong&gt;: &lt;code&gt;[String]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;body&lt;/strong&gt;: &lt;code&gt;JSON&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;id&lt;/strong&gt;: &lt;code&gt;ID!&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;_sys&lt;/strong&gt;: &lt;code&gt;SystemInfo!&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;_values&lt;/strong&gt;: &lt;code&gt;JSON!&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fields can be scalar (&lt;code&gt;Boolean&lt;/code&gt;, &lt;code&gt;Float&lt;/code&gt;, &lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt; and so on) or complex (containing other data). A trailing exclamation mark ( &lt;strong&gt;!&lt;/strong&gt; ) means the field is required (non-nullable). Square brackets mean an array. If the type is &lt;code&gt;JSON&lt;/code&gt;, then a set of sub-fields is defined. You can also use previously defined types to create relationships. For example, if your blog has multiple contributors you could have a field called &lt;code&gt;author&lt;/code&gt; with the type &lt;code&gt;User!&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Queries
&lt;/h4&gt;

&lt;p&gt;Because there's only one endpoint, you have to tell it what you want. For example, to get the list of collections you'd use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;collections&lt;/code&gt; filed is the &lt;em&gt;root field&lt;/em&gt;. Everything else is the &lt;em&gt;payload&lt;/em&gt;. Because the payload only contains &lt;code&gt;name&lt;/code&gt; the query returns a list of all the &lt;code&gt;collections&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"collections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blog"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"recipe"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get exactly the amount of information you ask for in the payload. You can also pass arguments. For example, if you wanted to get the &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;date&lt;/code&gt; and &lt;code&gt;draft&lt;/code&gt; status of this article you could use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"getting-started-with-graphql.md"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, typically you would use a &lt;a href="http://graphql.org/learn/queries/#variables" rel="noopener noreferrer"&gt;variable&lt;/a&gt; in the query, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$relativePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$relativePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables have a prefix ( &lt;strong&gt;$&lt;/strong&gt; ). The type (&lt;code&gt;String&lt;/code&gt; in this example) is defined in the query. The variable is passed in the &lt;code&gt;blog&lt;/code&gt; parameters.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mutations
&lt;/h4&gt;

&lt;p&gt;Query is the equivalent of &lt;code&gt;GET&lt;/code&gt; in REST or &lt;code&gt;read&lt;/code&gt; in traditional data terminology. For &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt; and &lt;code&gt;delete&lt;/code&gt; there's &lt;code&gt;mutation&lt;/code&gt;. The syntax is the same as for queries. You might have noticed the &lt;code&gt;ID&lt;/code&gt; type earlier. If you specify it when creating a record, the GraphQL server will give it a unique identifier. You can try out this example from the TinaCMS &lt;a href="https://tina.io/docs/graphql/queries/update-document/" rel="noopener noreferrer"&gt;documenation&lt;/a&gt; (sticking with the &lt;a href="https://www.imdb.com/title/tt0374900/" rel="noopener noreferrer"&gt;Napoleon Dynamite&lt;/a&gt; references):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;updatePost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"voteForPedro.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vote For Napolean Instead"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"politics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content/authors/napolean.json"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Subscriptions
&lt;/h4&gt;

&lt;p&gt;I've written previously about event-driven architectures. Subscriptions are a way to get data from the GraphQL server every time an subscribed event takes place. Not all GraphQL servers are configured to support websocket subscriptions, and that's true of my TinaCMS instance. But if it was, this is what a request to be notified when a new blog article is created would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;subscription&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;newBlog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Custom scalars
&lt;/h4&gt;

&lt;p&gt;Five months after I originally wrote this article, I ran into an issue with Magidoc. The doc build was failing with missing definitions for custom scalars. At first, I thought this was a bug, but it turns out that the bug was that the previous version would build the docs even if the definitions were missing. You don't need to provide definitions for the predefined scalars (&lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Int&lt;/code&gt;, &lt;code&gt;Float&lt;/code&gt;, &lt;code&gt;Boolean&lt;/code&gt; and &lt;code&gt;ID&lt;/code&gt;). But if you have any custom scalars in your schema you must provide an example for each in JSON format in the Magidoc config file. These definitions go under &lt;code&gt;options&lt;/code&gt; in the &lt;code&gt;website&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;website:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;template:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'carbon-multi-page'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;customStyles:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'/static/css/custom.css'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;options:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;queryGenerationFactories:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;accountID:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01234567-890a-bcde-f012-34567890abcd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;It's beyond the scope of this article to do more than cover the basics. For more information, the GraphQL &lt;a href="https://graphql.org/learn/" rel="noopener noreferrer"&gt;documenation&lt;/a&gt; is a good starting point. TinaCMS is a good application to start experimenting with its &lt;a href="https://tina.io/docs/graphql/overview/" rel="noopener noreferrer"&gt;GraphQL API&lt;/a&gt;. For the adventurous, Kingdom Orjiewuru wrote the first part of an &lt;a href="https://medium.com/software-insight/building-a-simple-document-manager-with-graphql-part-1-ccdb1707f8f" rel="noopener noreferrer"&gt;article&lt;/a&gt; on building a simple document manager with GraphQL.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>tinacms</category>
      <category>graphql</category>
      <category>magidoc</category>
    </item>
    <item>
      <title>Why you should try programming in Lua</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Sun, 06 Apr 2025 12:49:15 +0000</pubDate>
      <link>https://dev.to/aowendev/why-you-should-try-programming-in-lua-2ic6</link>
      <guid>https://dev.to/aowendev/why-you-should-try-programming-in-lua-2ic6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let me start by saying that I don't have anything against Python. It's the number one programming language for a reason. But I learned Perl before Python was invented, and I've never had a compelling reason to learn it. On the other hand, when I worked in video games, I needed to get familiar with &lt;a href="https://www.lua.org/" rel="noopener noreferrer"&gt;Lua&lt;/a&gt; and I found it very much to my liking.&lt;/p&gt;

&lt;p&gt;Lua (meaning moon in Portuguese) is 30 years old this year. It was created by Waldemar Celes, Luiz Henrique de Figueiredo and Roberto Ierusalimschy at the Computer Graphics Technology Group of the Pontifical Catholic University of Rio de Janeiro. Brazil's trade barriers for computer software and hardware meant that it was natural for the department to create its own efficient, lightweight, embeddable scripting language.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Because many potential users of the language were not professional programmers, the language should avoid cryptic syntax and semantics. The implementation of the new language should be highly portable, because Tecgraf's clients had a very diverse collection of computer platforms. Finally, since we expected that other Tecgraf products would also need to embed a scripting language, the new language should follow the example of SOL and be provided as a library with a C API.—"The Evolutino of Lua"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In its current version, Lua supports data-driven, functional, object-oriented and procedural programming. It combines simple procedural syntax with data description constructs based on associative arrays and extensible semantics. It's dynamically typed, runs by interpreting byte code with a register-based virtual machine and has automatic memory management with incremental garbage collection.&lt;/p&gt;

&lt;p&gt;During the seventh console generation (PlayStation 3, Wii, Xbox 360), one games industry &lt;a href="https://www.satori.org/2009/03/05/the-engine-survey-general-results/" rel="noopener noreferrer"&gt;survey&lt;/a&gt; found that more than half its respondents were using Lua for scripting. This makes a lot of sense when you consider that the leading game engine at the time was Unreal, and the leading game development language was C/C++. More than a fifth of respondents were also using Lua for rapid prototyping. Not only is Lua fast, portable, embeddable, simple but powerful, it's also small at around 30000 lines of code. And it's distributed under the &lt;a href="https://opensource.org/license/mit/" rel="noopener noreferrer"&gt;MIT License&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When parents decided to teach their kids programming, they will often start with block-based languages like &lt;a href="https://scratch.mit.edu/" rel="noopener noreferrer"&gt;Scratch&lt;/a&gt;. These can teach programming principles and generate an enthusiasm for programming. But prolonged exposure could make it harder to transition to line-based programming. Python is a popular choice of teaching language, except with mean Gen X parents who make their kids use &lt;a href="https://en.wikipedia.org/wiki/BASIC" rel="noopener noreferrer"&gt;BASIC&lt;/a&gt;. Lua is a little more niche, but it has some cool add-ons like the &lt;a href="https://leafo.net/lapis/" rel="noopener noreferrer"&gt;Lapis&lt;/a&gt; web framework and the &lt;a href="https://love2d.org/" rel="noopener noreferrer"&gt;LÖVE&lt;/a&gt; 2D-games framework, and it's used in some interesting places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://codea.io/" rel="noopener noreferrer"&gt;Codea&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lightroom.adobe.com/" rel="noopener noreferrer"&gt;Lightroom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cc-tweaked/CC-Tweaked/" rel="noopener noreferrer"&gt;Minecraft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pandoc.org/lua-filters.html" rel="noopener noreferrer"&gt;Pandoc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/PhyreEngine" rel="noopener noreferrer"&gt;PhyreEngine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pico-8.fandom.com/wiki/Lua" rel="noopener noreferrer"&gt;Pico-8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://create.roblox.com/docs/luau" rel="noopener noreferrer"&gt;Roblox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://torch.ch/" rel="noopener noreferrer"&gt;Torch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.videolan.org/Documentation:Modules/lua/" rel="noopener noreferrer"&gt;VLC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.waze.com/" rel="noopener noreferrer"&gt;Waze&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are two commercial projects I'm particularly fond of. For iPad owners, Codea gives you a way to develop apps directly on the device. It was created by University of Adelaide PhD students John Millard, Simeon Saëns and Dylan Sale. It's mainly aimed at games development, but it can be used for general purpose apps too. Complete projects can be exported directly into Xcode. It supports 3D through voxels and models in Wavefront format (as exported by Blender). Its frameworks include animation, graphics, location, motion, multitouch, network, physics, sound and storage. The first ever app store app made entirely on the iPad was made with Codea. Although currently, the only featured game still available is &lt;a href="https://apps.apple.com/us/app/cargo-bot/id519690804" rel="noopener noreferrer"&gt;Cargo-Bot&lt;/a&gt;. It has comprehensive documentation and comes with sample projects to get you started.&lt;/p&gt;

&lt;p&gt;For people who like the retro aesthetic but don't want to learn 6502 or Z80 assembly language, the Pico-8 is a fantasy console that runs in a web browser or on Linux, macOS and Windows. It has a 128×128 pixels display with 16 colors from a palette of 256 colors. There are 256 8×8 pixel sprites and a 128×32 tile map. It has a 4-channel programmable sound generator. There are editors for code, music, sound, sprites and maps built into the console. Cartridges are stored as PNG files. Pico-8 also includes &lt;a href="https://www.lexaloffle.com/pico-8.php?page=schools" rel="noopener noreferrer"&gt;licensing for educators&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Afterword
&lt;/h4&gt;

&lt;p&gt;As is often the case, I learned about another project after publishing. The &lt;a href="https://tic80.com/" rel="noopener noreferrer"&gt;TIC-80&lt;/a&gt; is an open source alternative to the Pico-8 that also ssupports Lua. It too has built-in editors for code, sprites, maps, sound and so on. It has a 240×136 pixel display with a fixed 16 color palette that is quite similar to the &lt;a href="https://lospec.com/palette-list/dawnbringer-16" rel="noopener noreferrer"&gt;DawnBringer 16&lt;/a&gt; palette. The TIC-80 has proved particularly popular at in the computer &lt;a href="https://tic80.com/play?cat=5" rel="noopener noreferrer"&gt;demoscene&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>codea</category>
      <category>pico8</category>
      <category>gamedev</category>
      <category>lua</category>
    </item>
    <item>
      <title>An introduction to the semantic web</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Sun, 06 Apr 2025 12:47:18 +0000</pubDate>
      <link>https://dev.to/aowendev/an-introduction-to-the-semantic-web-2o33</link>
      <guid>https://dev.to/aowendev/an-introduction-to-the-semantic-web-2o33</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of my predictions for 2023 was that there would be a lot more talk about Web 3.0. I couldn't have been more wrong. Global events and the rise of AI have completely overshadowed web developments. But it's still a topic worth some consideration.&lt;/p&gt;

&lt;p&gt;The term Web 2.0 was coined by Darcy DiNucci in 1999. Web 1.0 was subsequently invented as a term to describe the earlier period. There is no fixed delineation between the two eras. The first is generally thought to have lasted from 1989 to 2004 and featured mainly static content. The second is thought to start when social media profiles replaced personal web pages.&lt;/p&gt;

&lt;p&gt;But I'd draw a different distinction. I think of Web 1.0 as everything before HTML5 (or the &lt;a href="https://en.wikipedia.org/wiki/Adobe_Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt; era). When the iPhone was announced in January 2007, initially it wasn't supposed to run native apps, except for the basic set included on the device. It was supposed to run web apps (written with &lt;a href="https://en.wikipedia.org/wiki/Ajax_(programming)" rel="noopener noreferrer"&gt;Ajax&lt;/a&gt;). HTML5 launched the next year.&lt;/p&gt;

&lt;p&gt;Web 3.0 means different things to different people. It's sometimes used as an alternative term for Web3, which is an idea for a version of the web featuring decentralization, blockchain and token-based economics. It's where non-fungible tokens (&lt;a href="https://en.wikipedia.org/wiki/Non-fungible_token" rel="noopener noreferrer"&gt;NFTs&lt;/a&gt;) came from. But it's not well-defined or widely adopted.&lt;/p&gt;

&lt;p&gt;In his 2000 book “&lt;a href="https://wordery.com/9780062515872" rel="noopener noreferrer"&gt;Weaving the Web&lt;/a&gt;”, Tim Berners-Lee described a vision where computers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"…become capable of analyzing all the data on the Web – the content, links, and transactions between people and computers. A 'Semantic Web', which makes this possible, has yet to emerge, but when it does, the day-to-day mechanisms of trade, bureaucracy and our daily lives will be handled by machines talking to machines. The 'intelligent agents' people have touted for ages will finally materialize."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The semantic web is also sometimes known as Web 3.0. But Berners-Lee wasn't the first to have this vision. Arguably, it started with Ted Nelson and &lt;a href="https://en.wikipedia.org/wiki/Project_Xanadu" rel="noopener noreferrer"&gt;Project Xanadu&lt;/a&gt; in 1960. And the ideas that influence it can be traced back even earlier to Vanneavar Bush's 1945 article “&lt;a href="https://en.wikipedia.org/wiki/As_We_May_Think" rel="noopener noreferrer"&gt;As We May Think&lt;/a&gt;”. For a deeper dive I recommend watching Douglas Adams's 1990 documentary “&lt;a href="https://en.wikipedia.org/wiki/Hyperland" rel="noopener noreferrer"&gt;Hyperland&lt;/a&gt;” which predates the World Wide Web and the first web browser.&lt;/p&gt;

&lt;p&gt;Hyperland does a remarkable job of predicting the modern internet, developments in virtual reality and software agents like Siri (although Tom Baker's agent is rather more configurable). But I'm in the minority camp that thinks that Xanadu would have been better than what we have now. Its original rules stated that every document can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consist of any number of parts, each of which may be of any data type.&lt;/li&gt;
&lt;li&gt;Contain links of any type, including virtual copies to any other document in the system accessible to its owner.&lt;/li&gt;
&lt;li&gt;Contain a royalty mechanism at any desired degree of granularity to ensure payment on any portion accessed, including virtual copies of all or part of the document.&lt;/li&gt;
&lt;li&gt;Have secure access controls.&lt;/li&gt;
&lt;li&gt;Be rapidly searched, stored and retrieved without user knowledge of where it is physically stored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every server, user, document and auditable transaction would be uniquely and securely identified. Documents would automatically be moved to physical storage appropriate to frequency of access from any given location. Documents would automatically be stored redundantly to maintain availability even in case of a disaster. Blockchain will have a role to play if we ever get there.&lt;/p&gt;

&lt;p&gt;That leaves us with the semantic web, which exists now. But what are semantics? The term is derived from the earlier semiotics (the interpretation of signs and symbols). It can mean the study and classification of changes in significance of words, or a branch of semiotics to do with relations between signs and what they refer to. But in web terms, we'd probably just call it metadata.&lt;/p&gt;

&lt;p&gt;One way of adding metadata to web pages is using the &lt;a href="https://ogp.me/" rel="noopener noreferrer"&gt;Open Graph Protocol&lt;/a&gt;. It was originally developed by Facebook (Meta) for use with its Social Graph mapping and tracking tool. Meta uses it to enable any web page to have the same functionality as any other object on Facebook. But other social networks also use it. The basic metadata includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;og:title&lt;/code&gt;  Title of the object as it should appear within the graph. Example: “The Rock”.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;og:type&lt;/code&gt; Type of object. Example: “video.movie”.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;og:image&lt;/code&gt; Image URL that should represent your object within the graph.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;og:url&lt;/code&gt; Canonical URL of your object that will be used as its permanent ID in the graph. Example: “https://www.imdb.com/title/tt0117500/”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On this site with Hugo, I include these tags in &lt;code&gt;head.html&lt;/code&gt; partial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"{{.Title}}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"article"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"{{.Params.Image | absURL}}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that when you click one of the social share buttons, it should pick up the correct image. Before I did this, it would default to the background that goes behind the header.&lt;/p&gt;

&lt;p&gt;Tags have long been used for search engine optimization (SEO). Here are some commonly recommended tags to include in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag on an HTML page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;A clickbait title&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"A description of the content."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"author"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Your Name"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The viewport tag ensures the browser window is an appropriate size for the screen of the device. You should also include &lt;code&gt;&amp;lt;meta name="robots" content="noindex"&amp;gt;&lt;/code&gt; on pages that you don't want to be indexed, such as error pages. And you should always include the &lt;code&gt;alt&lt;/code&gt; attribute on images. But you can go much deeper and add microdata to your content with &lt;a href="https://www.schema.org/docs/gs.html" rel="noopener noreferrer"&gt;schema.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To conclude, while we're waiting for Web 3.0 to arrive, it's a good idea to start using metadata. If nothing else, it will be useful to our future robot overlords.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>metadata</category>
      <category>web3</category>
      <category>semanticweb</category>
    </item>
    <item>
      <title>Getting started with Bitbucket Pipelines</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Sun, 06 Apr 2025 12:43:26 +0000</pubDate>
      <link>https://dev.to/aowendev/getting-started-with-bitbucket-pipelines-kmh</link>
      <guid>https://dev.to/aowendev/getting-started-with-bitbucket-pipelines-kmh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm a big fan of GitHub Actions. But if you're working for an enterprise software company, there's a fair chance you're using Atlassian's Bitbucket Cloud (along with Confluence and Jira). If so, then you can use Pipelines to build continuous integration and deployment workflows. If you're new to DevOps and CI/CD, I have a &lt;a href="https://dev.to/aowendev/using-github-actions-and-hosted-runners-for-continuous-integration-and-delivery-29ik"&gt;TL:DR&lt;/a&gt; for you.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In Bitbucket, go to your repository and select &lt;strong&gt;Pipelines&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create your first pipeline&lt;/strong&gt; to scroll down to the template section.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Starter&lt;/strong&gt; pipeline. This will create a file called &lt;code&gt;bitbucket-pipelines.yml&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Previously, I wrote a GitHub Action to unpack a zip archive. So let's recreate that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlassian/default-image:3&lt;/span&gt;

&lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;extract&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;zip&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;file'&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;changesets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;includePaths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.zip"&lt;/span&gt;
        &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rm -r uploads/extracted&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;filename=$(basename -s .zip *.zip)&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;unzip *.zip&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rm *.zip&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mv $filename temp&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mv temp/out/* .&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rm -r temp&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git add .&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git commit -m "unzip"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git push origin main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you're done, click &lt;strong&gt;Commit File&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Bitbucket Pipelines runs your builds in Docker containers. So first you need to choose an image. The default is atlassian/default-image:latest. Atlassian recommends using this until you get your pipeline working and then finding a specific image. So in this instance, we're using Ubuntu 20.04 LTS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;default&lt;/strong&gt; tells the script to run when a commit is pushed to any branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;parallel&lt;/strong&gt; enables you to run steps simultaneously, but it's not necessary here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;step&lt;/strong&gt; is an individual task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;name&lt;/strong&gt; is the display name of the step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;condition&lt;/strong&gt; limits the circumstances under which the script runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;changesets&lt;/strong&gt; specifies changes to use as a condition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;includePath&lt;/strong&gt; sets the file path that will match the condition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;script&lt;/strong&gt; is the Linux CLI input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The script is virtually identical to the GitHub Action I wrote, so for a more detailed explanation, you can read &lt;a href="//../using-github-actions-to-automatically-unpack-a-zip-archive/"&gt;that&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Image: original by &lt;a href="https://unsplash.com/photos/4CNNH2KEjhc" rel="noopener noreferrer"&gt;Sigmund&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>git</category>
      <category>devops</category>
      <category>bitbucket</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Adding languages to a Hugo site</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Thu, 16 Mar 2023 08:44:33 +0000</pubDate>
      <link>https://dev.to/aowendev/adding-languages-to-a-hugo-site-2gfj</link>
      <guid>https://dev.to/aowendev/adding-languages-to-a-hugo-site-2gfj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm a long-term advocate for localization, but this site has been monolingual for over a year now. It's past time I started following my own advice. So last weekend I finally got around to localizing the site for French.&lt;/p&gt;

&lt;p&gt;As with most localization tasks, I'm retrofitting it to an existing project. Fortunately, I chose &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; as my static site generator, and it has built-in internationalization support using &lt;a href="https://github.com/nicksnyder/go-i18n" rel="noopener noreferrer"&gt;go-i18n&lt;/a&gt;. On the downside, I chose a theme that doesn't fully support localization, which meant there was some work involved. But not as much as I'd expected. So let me take you through the steps involved.&lt;/p&gt;

&lt;p&gt;First, I updated my &lt;code&gt;config.toml&lt;/code&gt; file to tell Hugo that my site is now multilingual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;defaultContentLanguageInSubdir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="py"&gt;DefaultContentLanguage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"en-us"&lt;/span&gt;
&lt;span class="nn"&gt;[languages]&lt;/span&gt;
  &lt;span class="nn"&gt;[languages.en-us]&lt;/span&gt;
    &lt;span class="py"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Andrew Owen | Writer | Designer"&lt;/span&gt;
    &lt;span class="py"&gt;languageName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"English"&lt;/span&gt;
    &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nn"&gt;[languages.fr]&lt;/span&gt;
    &lt;span class="py"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Andrew Owen | Écrivain | Concepteur"&lt;/span&gt;
    &lt;span class="py"&gt;languageName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Français"&lt;/span&gt;
    &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting &lt;code&gt;defaultContentLanguageInSubdir&lt;/code&gt; leaves the language out of the URL for the default language, in my case US English. Any settings you leave out from the language definitions will fall back to the default.&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;404.md&lt;/code&gt; page is already stored in a folder called &lt;code&gt;en&lt;/code&gt;. So adding a French version was just a case of creating a new folder called &lt;code&gt;fr&lt;/code&gt;, copying the file across and changing the text.&lt;/p&gt;

&lt;p&gt;The next step was to set up some a set of translatable phrases for the generated content. This is as simple as creating an &lt;code&gt;i18n&lt;/code&gt; folder in the root of the Hugo repository and adding a YAML or TOML file for each language. In my case &lt;code&gt;en-us.yaml&lt;/code&gt; and &lt;code&gt;fr.yaml&lt;/code&gt;. These phrases are included using the shortcode &lt;code&gt;{{T "phrase" | formatting}}&lt;/code&gt;. If you want to include HTML such as &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; tags, then use &lt;code&gt;safeHTML&lt;/code&gt; for the &lt;code&gt;formatting&lt;/code&gt; value. Each value should have an ID and a definition in each language file. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;January&lt;/span&gt;
  &lt;span class="na"&gt;translation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;janvier"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In several places in the site, titles were derived from data contained in YAML files. In each of these cases, I replaced the data-derived version with a shortcode to the translated version instead. I also had to modify some of the links to go to the correct place when viewing the site in French. In most cases, that just meant using the &lt;code&gt;.Site.Language.Lang&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;Because I'm only supporting two languages, I wanted to have a simple toggle. The nav bar was starting to get rather full, so I removed the &lt;code&gt;Home&lt;/code&gt; link, because the logo already takes you there. I had to change the RSS link to make it work in French, so I also replaced it with the icon. I figure anyone who still cares about RSS should recognize it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {{ if eq .Site.Language.Lang "en-us" }}
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-link"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/blog/index.xml"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'fa fa-rss'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  {{ else }}
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-link"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/fr/blog/index.xml"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'fa fa-rss'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  {{ end }}
&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last thing to do was to translate the individual articles. Hugo gives you two ways of doing this. You can set up a folder for each language, and if you have three or more languages, you should do this. But by default, Hugo will treat anything ending in &lt;code&gt;.md&lt;/code&gt; as the default language, and you can add other languages by adding the language code to the extension, for example: &lt;code&gt;.fr.md&lt;/code&gt;. If you're using folders, then you add the folder details to the language definitions in your TOML file. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[languages]&lt;/span&gt;
  &lt;span class="nn"&gt;[languages.en]&lt;/span&gt;
    &lt;span class="py"&gt;contentDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"content/english"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While I was editing the site in VScode, I also took the opportunity to reorganize the images. They were getting a bit tricky to navigate with TinaCMS, so I moved the blog images into folders by year. I also did some cleanup on the top nav bar. The last thing to do was change the article date string from &lt;code&gt;{{ .PublishDate.Format “2 January 2006” }}&lt;/code&gt; to &lt;code&gt;{{.Date.Day}} {{i18n .Date.Month}} {{.Date.Year}}&lt;/code&gt; after setting up a set of translatable date strings in the YAML files in the &lt;code&gt;i18n&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Of course, just when I thought I'd caught everything, I noticed that tag links were broken. I fixed this by making the URL relative so that it would pick up the current language. I'm pleased to say that I didn't have to do anything at all to localize search.&lt;/p&gt;

&lt;p&gt;With the technical piece done, all that's left to do is the translation. There are ways to automate the process, but there's still no substitute for a human reviewer. I'm using a combination of &lt;a href="https://www.deepl.com/translator" rel="noopener noreferrer"&gt;DeepL&lt;/a&gt;, &lt;a href="https://languagetool.org/" rel="noopener noreferrer"&gt;LanguageTool&lt;/a&gt; and my own modest language ability. To begin with, I'm only translating the core content. But eventually I intend to have a fully translated site. If you are a native French user, and you spot any egregious mistakes, please drop me an email.&lt;/p&gt;

</description>
      <category>hugo</category>
      <category>localization</category>
      <category>l10n</category>
      <category>i18n</category>
    </item>
    <item>
      <title>Sales for developers</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Thu, 19 Jan 2023 06:59:41 +0000</pubDate>
      <link>https://dev.to/aowendev/sales-for-developers-3n5d</link>
      <guid>https://dev.to/aowendev/sales-for-developers-3n5d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You're a developer. Have you ever wondered how the software you write gets into the hands of users? No, me either. In over 15 years in IT, I never gave a thought to the sales process. But in my role as a solutions engineer, I've had to take a crash course. And now I'm convinced that everyone who isn't sales should at least understand something about it. Fortunately, my MasterClass subscription is still valid, so I was able to take Daniel Pink's class on &lt;a href="https://www.masterclass.com/classes/daniel-pink-teaches-sales-and-persuasion" rel="noopener noreferrer"&gt;Sales and Persuasion&lt;/a&gt;. Previously, I watched the entire John Barrows YouTube sales tips &lt;a href="https://www.youtube.com/watch?v=h_y2WkZ2JEQ&amp;amp;list=PLNFgwC3AOW8QLLmeoGfNeF2cCULP5BkkX" rel="noopener noreferrer"&gt;playlist&lt;/a&gt; (if I hear someone say “make it happen” one more time, I'm going to scream). But unless you work at one of those companies where you're told “everyone is in sales”, you probably don't want to spend half a day watching those. So I'll reduce it down to the essentials for you.&lt;/p&gt;

&lt;h4&gt;
  
  
  Evaluating a prospect
&lt;/h4&gt;

&lt;p&gt;In the 1950s, IBM came up with a sales strategy known as BANT that's still in use today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Budget: Can the organization you're selling to afford what you're selling?&lt;/li&gt;
&lt;li&gt;Authority: Can the person you're dealing with approve the purchase?&lt;/li&gt;
&lt;li&gt;Need: Does what you're selling solve the organization's problems?&lt;/li&gt;
&lt;li&gt;Timeline: When will a purchase decision be made?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If they can't afford or don't need what you're selling, thank them for their time and move on. If your contact can't approve the purchase, involve the person who can. If the decision is more than a year away, park it for now and revisit closer to the time.&lt;/p&gt;

&lt;h4&gt;
  
  
  Consecutive selling
&lt;/h4&gt;

&lt;p&gt;It's time for another acronym. In the 1980s, &lt;a href="https://en.wikipedia.org/wiki/Neil_Rackham" rel="noopener noreferrer"&gt;Neil Rackham&lt;/a&gt; came up with a series of questions to use in large sales deals known as SPIN-selling that's still in use today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Situation: Ask about the pain points and expectations to determine goals and needs.&lt;/li&gt;
&lt;li&gt;Problem: Ask leading questions to identify problems, such as “how much time a day do you spend on that?”&lt;/li&gt;
&lt;li&gt;Implication: Ask about the consequences of the problems, such as “how much time does that waste a month?”&lt;/li&gt;
&lt;li&gt;Need-payoff: Ask questions to lead the prospect to their own positive conclusions, such as “if you could be more productive, what effect would that have on your revenue?”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are four stages, but no acronym this time, unless you mistype PAID:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Preliminaries: Introduction. Don't talk about your product or service.&lt;/li&gt;
&lt;li&gt;Investigation: Situation questions. Don't talk about your product or service.&lt;/li&gt;
&lt;li&gt;Demonstration of capabilities: Implication and Need-payoff questions. Talk about your product or service in terms of (another acronym) FABs: “Product [feature], enables you to [advantage] which means you get [benefit].”&lt;/li&gt;
&lt;li&gt;Acquiring commitment: Close the deal. Ideally, set a date for a yes / no call.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  The new rules
&lt;/h4&gt;

&lt;p&gt;“&lt;a href="https://www.imdb.com/title/tt0104348/" rel="noopener noreferrer"&gt;Glengarry Glen Ross&lt;/a&gt;” is a great film. But it's a terrible way to sell. Sellers used to have all the information and buyers were at their mercy. The internet changed all that. Be an active listener. Help the prospect to get to the root cause of the problems they are trying to solve. With luck, you've got the solution. If you don't, be honest about it. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“When people have their own reasons for doing something, they're more likely to do it.”—Daniel Pink&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Less is more
&lt;/h4&gt;

&lt;p&gt;Too much choice is a bad thing. In 1997 Apple offered 12 models of Macintosh. On his return, Steve Jobs narrowed it to four models and two choices: consumer or professional; desktop or laptop.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mimicry works
&lt;/h4&gt;

&lt;p&gt;Use the prospect's own language. This may be the only truism about selling that's backed up by &lt;a href="https://www.bookdepository.com/Invisible-Influence-Jonah-Berger/9781476759739" rel="noopener noreferrer"&gt;research&lt;/a&gt;. If you're uncomfortable doing it on purpose, don't worry; you're probably doing it without thinking about it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Be respectful of people's time
&lt;/h4&gt;

&lt;p&gt;Everyone is busier than ever. If you want people to give you their time, make it worth their while.&lt;/p&gt;

</description>
      <category>software</category>
      <category>development</category>
      <category>sales</category>
      <category>time</category>
    </item>
    <item>
      <title>Running Intel binaries in Debian ARM with Rosetta</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Thu, 12 Jan 2023 08:29:16 +0000</pubDate>
      <link>https://dev.to/aowendev/running-intel-binaries-in-debian-arm-with-rosetta-3b0j</link>
      <guid>https://dev.to/aowendev/running-intel-binaries-in-debian-arm-with-rosetta-3b0j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've mentioned UTM before. It's a nice wrapper for &lt;a href="https://www.qemu.org/" rel="noopener noreferrer"&gt;QEMU&lt;/a&gt; that enables you to create ARM virtual machines and emulate non-ARM machines on macOS. It's a free download from the &lt;a href="https://mac.getutm.app/" rel="noopener noreferrer"&gt;website&lt;/a&gt;, or you can get it in the &lt;a href="https://apps.apple.com/us/app/utm-virtual-machines/id1538878817?mt=12" rel="noopener noreferrer"&gt;app store&lt;/a&gt;. But one of the features I've been looking forward to is being able to use Rosetta to do X64 to ARM64 instruction translation, which is supported in the latest version of UTM on macOS Ventura. I was hoping to be able to install Intel VMs using Rosetta, but for that you still have to use QEMU. What you can do is install a Debian ARM VM, enable Rosetta, and then run X64 Debian packages on that VM. This can be useful if there's a particular package you need that doesn't have a native ARM build. Thus far I've only got it to run packages, and not individual Intel binaries. There is also a big caveat:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is a bug present in Linux virtual machines on Ventura and the base M1 chip that causes the virtual machine to randomly kernel panic and freeze. Unfortunately, this means that base M1 users should avoid Apple Virtualization backend until Apple or Linux maintainers provide a fix.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm running on an M1 Pro, and that seems to work well. After you've installed UTM you'll need the &lt;a href="https://cdimage.debian.org/debian-cd/current/arm64/iso-cd/" rel="noopener noreferrer"&gt;Debian Net Installer&lt;/a&gt;. You'll also need to &lt;a href="https://support.apple.com/en-us/HT211861" rel="noopener noreferrer"&gt;install Rosetta&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Set up the VM
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Open UTM and click Add ( ➕ ) to open the VM creation wizard.&lt;/li&gt;
&lt;li&gt;On the Start panel, click Virtualize, then on the Operating System panel, click Linux.&lt;/li&gt;
&lt;li&gt;On the Linux panel, select Use Apple Virtualization, then select Enable Rosetta (x86_64 Emulation).&lt;/li&gt;
&lt;li&gt;Click Browse and select the Debian installer ISO you downloaded earlier, then click Next.&lt;/li&gt;
&lt;li&gt;Choose the amount of RAM and CPU cores you want the VM to use, then click Next. I used the default settings.&lt;/li&gt;
&lt;li&gt;Set the maximum drive space to allocate, then click Next. I left this at the default 64GB. The image won't take up the full amount when you create it, and can be trimmed if you have sufficient disk space for a copy, so you should set this to the maximum amount you think you'll need.&lt;/li&gt;
&lt;li&gt;(Optional) Choose a shared folder to mount in the VM, then click Next. If you skip this step, you can select the folder later from the VM toolbar.&lt;/li&gt;
&lt;li&gt;Click Save to create the VM and then click Run ( &lt;a href="https://emojipedia.org/play-button/" rel="noopener noreferrer"&gt;▶️ &lt;/a&gt;) to start the VM.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Install Debian
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;When the VM boots, select Install and follow the setup wizard.&lt;/li&gt;
&lt;li&gt;When formatting the disk, install to Virtual disk 1 (vda), using entire disk, all files in one partition&lt;/li&gt;
&lt;li&gt;From Software selection, select GNOME, SSH server and standard system utilities.&lt;/li&gt;
&lt;li&gt;After installation, eject the disk and reboot.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Enable sudo
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;whoami&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;su &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;span class="c"&gt;# /usr/sbin/usermod -aG sudo $USERNAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you enable sudo for the default user, you must restart the VM for the change to take effect.&lt;/p&gt;

&lt;h4&gt;
  
  
  Install the software
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Install the guest tools: sudo apt install spice-vdagent.&lt;/li&gt;
&lt;li&gt;Install binary format support: sudo apt install binfmt-support.&lt;/li&gt;
&lt;li&gt;Create a mount point: sudo mkdir /media/rosetta.&lt;/li&gt;
&lt;li&gt;Mount Rosetta: sudo mount -t virtiofs rosetta /media/rosetta.&lt;/li&gt;
&lt;li&gt;Add this line to /etc/fstab: rosetta /media/rosetta  virtiofs    ro,nofail   0   0.&lt;/li&gt;
&lt;li&gt;Register Rosetta as an X64 ELF handler:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; / usr / sbin / update - binfmts--install rosetta / media / rosetta / rosetta &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--magic&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;7fELF&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;01&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;01&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;3e&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--mask&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;fe&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;fe&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;fe&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ff"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--credentials&lt;/span&gt; yes--preserve no--fix - binary &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic parameter describes the first 20 bytes of the ELF header for X64 binaries. The Linux kernel performs a bitwise logical AND with the first 20 bytes of any binary you try to run with the mask value. If there's a match, it uses the registered handler to interpret the binary. Otherwise, it reports an error.&lt;/p&gt;

&lt;p&gt;7. Enable X64 packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg &lt;span class="nt"&gt;--add-architecture&lt;/span&gt; amd64
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can now install and run any X64 package in the Debian repository with &lt;code&gt;sudo apt install packagename:amd64&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Install WINE
&lt;/h4&gt;

&lt;p&gt;One such package is WINE, which will enable you to run some 64-bit Windows binaries. But you'll also need to install WINE32 to get it to launch. I'm not sure if Rosetta can be persuaded to run 32-bit binaries as well. I'll have to do some further investigation. You'll also need to enable X86 pakcages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg &lt;span class="nt"&gt;--add-architecture&lt;/span&gt; i386
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can install WINE with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;wine64:amd64
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;wine32:i386
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far, I've only got the built-in file explorer working (launch with &lt;code&gt;wine64 explorer&lt;/code&gt;). If I get 32-bit apps working I'll write a new post on it.&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to offend most of your international users all at once</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Mon, 19 Dec 2022 18:49:50 +0000</pubDate>
      <link>https://dev.to/aowendev/how-to-offend-most-of-your-international-users-all-at-once-5h04</link>
      <guid>https://dev.to/aowendev/how-to-offend-most-of-your-international-users-all-at-once-5h04</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We love icons. They're a great way to convey information simply, even if many of them are skeuomorphs from a bygone age. You know, like using a floppy disk ( 💾 ) to mean save; a telephone receiver ( 📞 ) for voice calls; an envelope ( ✉️ ) for email; a single lens reflex camera ( 📷 ) for taking a picture; a movie camera ( 🎥 ) for video; a folder ( 📁 ) for file containers; a calendar ( 📅 ) for dates; a newspaper ( 📰 ) for news feeds; a spiral notepad ( 🗒️ ) for text editors; an alarm clock ( ⏰ ) for alerts; a stopwatch ( ⏱️ ) for timers; a bed ( 🛏️ ) for sleep; a book ( 📖 ) for electronic publications; and so on. And I took most of those examples from the current version of iOS nearly a decade after Apple supposedly abandoned skeuomorphism. But we keep on using them because they are unambiguous, with a one-to-one meaning. Unlike say, flags.&lt;/p&gt;

&lt;p&gt;Flags are problematic. All of them, not just the ones banned in Forza as “notorious iconography”. Typically, they represent a cultural identity that even those who reside within the state that they represent may not be comfortable with. They can also be unnecessary reminders of past wars, territorial disputes and current conflicts. And unlike icons, there is no one-to-one match between flags and languages. The same language may be spoken in multiple states. It is common for states to have at least two widely spoken languages. There are multiple states that use the same language. Some states have flags that look very similar to the flag of other states. And that's just scratching the surface of the problem.&lt;/p&gt;

&lt;p&gt;The W3C has this to say about it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't use flags to indicate languages! Flags represent countries, not languages. Numerous countries use the same language as another country, and numerous countries have more than one official language. Flags don't map well onto these permutations. In addition, flags have nationalistic connotations that may be unwelcome for people of other countries, even though they speak the same language. While flags can be very appropriate for sites that are distinguished on the basis of region (e.g. the amazon.co.uk site vs. the amazon.ca site), you should normally avoid them when dealing with links to pages that are just translations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that misses the point that Ireland typically gets lumped in with the UK due to the shared use of English (at time of writing, &lt;a href="http://www.amazon.ie" rel="noopener noreferrer"&gt;www.amazon.ie&lt;/a&gt; redirects to &lt;a href="http://www.amazon.co.uk" rel="noopener noreferrer"&gt;www.amazon.co.uk&lt;/a&gt;). So what's the solution? Use the &lt;a href="https://en.wikipedia.org/wiki/ISO_639-1" rel="noopener noreferrer"&gt;ISO two-letter language codes&lt;/a&gt;. And, optionally, you can include a hover text with the name of the language in that language. For example, FR [Français]. If you need to distinguish between variants of the same language, you can use the subtags from the &lt;a href="https://en.wikipedia.org/wiki/IETF_language_tag" rel="noopener noreferrer"&gt;IETF language tags&lt;/a&gt;. For example, PT-BR [português brasileiro]. If you're convinced, you could stop reading now. If not, here are some example problems.&lt;/p&gt;

&lt;h6&gt;
  
  
  Brazil 🇧🇷
&lt;/h6&gt;

&lt;p&gt;Commonly used to represent Portuguese. There are over 100 Amerindian languages spoken in Brazil. There are also differences between Brazilian Portuguese and the Portuguese used in Portugal. &lt;/p&gt;

&lt;h6&gt;
  
  
  Chad 🇹🇩
&lt;/h6&gt;

&lt;p&gt;Indistinguishable from the flag of Romania. The two official languages are French and Arabic. Setting aside using the flag of France for French, which flag are you going to choose for Arabic that isn't going to upset the occupants of at least one other Arabic-speaking nation?&lt;/p&gt;

&lt;h6&gt;
  
  
  France 🇫🇷
&lt;/h6&gt;

&lt;p&gt;Commonly used to represent French. France has many regional languages, including Basque, Breton, Catalan and Corsican. French is one of the main languages in countries including Belgium and Switzerland. French is also a major language in many former French colonies that aren't too happy about having been colonized by France.&lt;/p&gt;

&lt;h6&gt;
  
  
  Germany 🇩🇪
&lt;/h6&gt;

&lt;p&gt;Commonly used to represent German. Germany has many regional languages, including, Low Rhenish, North Frisian and Saterland Frisian. German is also the main language in Austria and one of the main languages in Switzerland.&lt;/p&gt;

&lt;h6&gt;
  
  
  India 🇮🇳
&lt;/h6&gt;

&lt;p&gt;The main two languages are Hindi and English. But there are hundreds of languages in use. At time of writing, the constitution mentions 22 languages, with demands to add another 38.&lt;/p&gt;

&lt;h6&gt;
  
  
  Mexico 🇲🇽
&lt;/h6&gt;

&lt;p&gt;Commonly used to represent Spanish. The federal government recognizes over 50 linguistic groups and over 300 varieties of indigenous languages. Spanish is spoken throughout South America, but there is no homogenous Latin American dialect. There are at least 16 languages besides Spanish and Portuguese used in South America with at least 125,000 speakers. Spanish as spoken in the Americas differs from that spoken in Spain. Spain itself has many regional languages, including Basque, Catalan and Galician.&lt;/p&gt;

&lt;h6&gt;
  
  
  United Kingdom 🇬🇧
&lt;/h6&gt;

&lt;p&gt;Commonly used to represent English. English is an official language in over 50 states, although that doesn't include the United Kingdom, the United States, Australia or New Zealand. At the time of writing, the only official language in the UK is Welsh, and the only official flags are the Welsh and Scots flags. As I've mentioned before in other posts, there are two distinct spelling systems (Cambridge; -ise endings, and Oxford; -ize endings). Cambridge is used in most of the former colonies, except Canada (which has its own versions of English and French). The IETF advocates using the two-letter country code in addition to the ISO two-letter language code, but that produces EN-GB, which doesn't tell you if you're using Cambridge or Oxford. Also, much to the annoyance of the English, American is the most commonly used English dialect. I therefore propose the following codes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(American) English: EN (the lingua franca, to the annoyance of the French)&lt;/li&gt;
&lt;li&gt;Australian English: EN-AU (the official Australian version)&lt;/li&gt;
&lt;li&gt;Cambridge English: EN-IE (same as Hiberno-English, an official language of Ireland)&lt;/li&gt;
&lt;li&gt;Canadian English: EN-CA (any version of English that doesn't closely follow a single dialect)&lt;/li&gt;
&lt;li&gt;New Zealand English: EN-NZ (vowel-reduced English)&lt;/li&gt;
&lt;li&gt;Oxford English: EN-OX (English for classics scholars)&lt;/li&gt;
&lt;li&gt;South African English: EN-ZA (English where vowels are entirely optional)&lt;/li&gt;
&lt;li&gt;Strine: EN-OZ (the version of Australian English as it's actually used)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Image: The Esperanto flag, in case you were wondering.&lt;/p&gt;

</description>
      <category>l18n</category>
      <category>icons</category>
      <category>webdev</category>
      <category>uiux</category>
    </item>
    <item>
      <title>Converting images with Image Magick</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Mon, 19 Dec 2022 18:22:01 +0000</pubDate>
      <link>https://dev.to/aowendev/converting-images-with-image-magick-16kn</link>
      <guid>https://dev.to/aowendev/converting-images-with-image-magick-16kn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Photoshop is old. Really old. Well, in computer terms anyway. As of writing, it's currently on version 24.0. It was originally developed for the Mac in 1987 by Thomas Knoll, a PhD student at the University of Michigan, and his brother John, who worked for Industrial Light &amp;amp; Magic. Photoshop is a raster graphic (pixel image) editor. And because of its age, it has features for rendering 24-bit images (16 million colors) down to limited palettes, (which were a common feature of computers in the 1980s), that are missing from, or hard to use in, other image manipulation programs.&lt;/p&gt;

&lt;p&gt;Another piece of software dating from 1987 is Image Magick. It was created by John Cristy at DuPont to convert 24-bit images to 8-bit images (256 colors). In 1990 Adobe became the publisher of Photoshop, and DuPont assigned the copyright in its tool to  ImageMagick Studios LLC, a non-profit organization “dedicated to making software imaging solutions freely available”. So, if you can't afford over $200 a year for a Creative Cloud subscription just to convert 24-bit images to low color images, Image Magick is the obvious solution.&lt;/p&gt;

&lt;p&gt;Some hardware that definitely doesn't date from 1987, but is designed as if it did, is the Chloe 280SE (the retro computer project that I will talk endlessly about if given the opportunity). The project is still in development, and at present its highest resolution graphics mode is incredibly complicated. It has a non-linear bitmap of 256×192 pixels. This is overlaid with a non-linear attribute map of 32×192 cells (each 8×1 pixels in size) that determine the foreground and background color. Each 8 pixel cell is rendered by paring a bitmap byte with an attribute byte. The attribute byte is divided into three parts. The highest two bits select one of four color look up tables (CLUTs). The middle three bits set one of 8 background colors, and the lowest three bits set one of 8 foreground colors. Background and foreground colors are defined independently. This gives 16 colors per CLUT, for a maximum of 64 colors on screen. However, it is not possible to combine colors from different CLUTs. The colors are chosen from a fixed palette of 256 colors based on half the 9-bit RGB palette (3-bits are used for red and green, but only two for blue). These are stored in GRB (MSB) order. A complete image consists of a 6K bitmap, 6K attribute map and 64 byte palette.&lt;/p&gt;

&lt;p&gt;I said it was complicated. Fortunately, Edward Cree wrote an image conversion tool for Linux (scrplus) and Klaus Jahn made a Windows version (Image2ULAplus). These can both be used on a Mac with a suitable virtualization solution. However, the results can vary widely. This is the pre-conversion process if you have access to any version of Photoshop.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scale the image you want to convert to 256×192 pixels.&lt;/li&gt;
&lt;li&gt;Pattern dither the image to a uniform 128 colors.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Typically, Floyd-Steinberg or diffusion dither will give better results than pattern dither when reducing color depth. However, pattern dither seems to be the best method to reduce the visibility of the attribute cells. The palette has 8 levels for red and green, but only 4 for blue. Reducing the range to a uniform 128 colors translates to giving each color 5 levels in the input image. I don't know why, but that seems to give the best conversion results.&lt;/p&gt;

&lt;p&gt;The conversion process after opening the input image in Image2ULAplus is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Switch off:

&lt;ul&gt;
&lt;li&gt;Reduce colors to 8-bit.&lt;/li&gt;
&lt;li&gt;Use dithering&lt;/li&gt;
&lt;li&gt;Create HAM256 screen&lt;/li&gt;
&lt;li&gt;Stretch to SCREEN$ size.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Switch on:

&lt;ul&gt;
&lt;li&gt;Calculate palette&lt;/li&gt;
&lt;li&gt;CLUTs: 0, 1, 2, 3&lt;/li&gt;
&lt;li&gt;Create Timex screen&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Set maximum colors to 64.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Render&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Save the image in &lt;code&gt;.SCR&lt;/code&gt; format.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I've got a licensed copy of Photoshop CS6 on a 2010 MacBook Air. But I don't really want to have to get that out of storage every time I need to convert an image. So here's how to use Image Magick instead. First, you have to install it (unless it's pre-installed in your Linux distro): On Ubuntu, you'd use &lt;code&gt;sudo apt install imagemagick&lt;/code&gt; and invoke it with the &lt;code&gt;convert&lt;/code&gt; command. On macOS, the easiest way to install it is using &lt;a href="//../blog/managing-packages-on-macos-with-homebrew/"&gt;Homebrew&lt;/a&gt; with &lt;code&gt;brew install imagemagick&lt;/code&gt;. On Windows, you can download an &lt;a href="https://imagemagick.org/script/download.php#windows" rel="noopener noreferrer"&gt;executable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With experimentation, I've found that the best setting for conversion to the Chloe 280SE is: &lt;code&gt;magick input.jpg -resize 256x192 -ordered-dither o8x8,5,5,4 output.gif&lt;/code&gt;. This scales the image to 256×192 and then performs an ordered (pattern) dither on the image using an 8×8 pattern (which works well with 8×1 attributes). It applies a uniform palette with 5 levels of red and green, and 4 levels of blue (128 uniform colors in Photoshop gives approximately 5 levels for each color). The results vary, but for the test image that I've used in all my tutorials, Image Magick actually does a better job than Photoshop.&lt;/p&gt;

&lt;p&gt;Now that's just one use case. Back in my days as a tech writer, I mainly used it for making &lt;code&gt;.PNG&lt;/code&gt; images as small as possible for inclusion in online help files. And that's barely scratching the surface of what's possible with Image Magick. Fortunately, there's good &lt;a href="https://imagemagick.org/script/command-line-processing.php" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; and an active &lt;a href="https://github.com/ImageMagick/ImageMagick/discussions" rel="noopener noreferrer"&gt;community&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Image: Detail from cover of “Nubia Coronation Special” by David Mack. Copyright © &lt;a href="//dc.com"&gt;DC&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>imageconversion</category>
      <category>devops</category>
      <category>automation</category>
      <category>batchprocessing</category>
    </item>
    <item>
      <title>Using GitHub Actions to automatically unpack a zip archive</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Mon, 19 Dec 2022 18:19:37 +0000</pubDate>
      <link>https://dev.to/aowendev/using-github-actions-to-automatically-unpack-a-zip-archive-2di9</link>
      <guid>https://dev.to/aowendev/using-github-actions-to-automatically-unpack-a-zip-archive-2di9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I was recently working with some software that could push a &lt;code&gt;zip&lt;/code&gt; archive of content to a Git repository. However, what I really wanted was for the contents of the archive to be pushed to the repository. So I created a GitHub Action to do that for me. I've covered the format of GitHub Actions before. But to recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;name&lt;/strong&gt; is what gets displayed in the actions list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;on&lt;/strong&gt; sets the triggers:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;push&lt;/strong&gt; and &lt;strong&gt;pull&lt;/strong&gt; trigger the script on push and pull requests. If you don't specify &lt;strong&gt;branches&lt;/strong&gt;, they default to &lt;strong&gt;main&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;paths&lt;/strong&gt; specifies the paths to match. In this case, the script will trigger when a &lt;code&gt;zip&lt;/code&gt; file is pushed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;worklflow_dispatch&lt;/strong&gt; enables you to manually trigger the script from the actions list.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;jobs&lt;/strong&gt; defines one or more named tasks, in this example &lt;strong&gt;unzip&lt;/strong&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;runs-on&lt;/strong&gt; specifies the VM environment. If you can use Ubuntu, it's the cheapest option with hosted runners.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;steps&lt;/strong&gt; can be used to invoke actions such as &lt;strong&gt;checkout&lt;/strong&gt; (which fetches a copy of the repository to the VM) and to execute shell commands with &lt;strong&gt;name: run&lt;/strong&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;run&lt;/strong&gt; with a pipe character ( &lt;strong&gt;|&lt;/strong&gt; ) executes a multi-line script. Without it, a single line is executed.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;And here's the action I created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;extract a zip file&lt;/span&gt;

    &lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;**.zip'&lt;/span&gt;
      &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;unzip&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
              &lt;span class="s"&gt;rm -r css&lt;/span&gt;
              &lt;span class="s"&gt;rm -r en&lt;/span&gt;
              &lt;span class="s"&gt;rm -r fonts&lt;/span&gt;
              &lt;span class="s"&gt;rm -r js&lt;/span&gt;
              &lt;span class="s"&gt;filename=$(basename -s .zip *.zip)&lt;/span&gt;
              &lt;span class="s"&gt;unzip *.zip&lt;/span&gt;
              &lt;span class="s"&gt;rm *.zip&lt;/span&gt;
              &lt;span class="s"&gt;mv $filename temp&lt;/span&gt;
              &lt;span class="s"&gt;mv temp/out/* .&lt;/span&gt;
              &lt;span class="s"&gt;rm -r temp&lt;/span&gt;
              &lt;span class="s"&gt;git config user.name github-actions&lt;/span&gt;
              &lt;span class="s"&gt;git config user.email github-actions@github.com&lt;/span&gt;
              &lt;span class="s"&gt;git add .&lt;/span&gt;
              &lt;span class="s"&gt;git commit -m "unzip"&lt;/span&gt;
              &lt;span class="s"&gt;git push origin main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script is written for the Linux command line. Let's break it down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; css

    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; en
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; fonts
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea here is that the contents of the zip file should replace what is already in that particular branch of the repository. You might want to call the branch &lt;strong&gt;uploads&lt;/strong&gt;. The checkout action has already been run, but it's a good idea to clear out any known folders. The &lt;code&gt;-r&lt;/code&gt; tag makes the action recursive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; .zip &lt;span class="k"&gt;*&lt;/span&gt;.zip&lt;span class="si"&gt;)&lt;/span&gt;

    unzip &lt;span class="k"&gt;*&lt;/span&gt;.zip
    &lt;span class="nb"&gt;rm&lt;/span&gt; .zip
    &lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt; temp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script assumes that we don't know the name of the &lt;code&gt;zip&lt;/code&gt; file, but that there is only one file. It will determine the name, unzip the file to the root, remove the &lt;code&gt;zip&lt;/code&gt; file and rename the folder containing the zip to &lt;code&gt;temp&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mv &lt;/span&gt;temp/out/ &lt;span class="nb"&gt;.&lt;/span&gt;

    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; temp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the contents of the &lt;code&gt;zip&lt;/code&gt; file are two folders deep (in the &lt;code&gt;out&lt;/code&gt; folder). This moves the contents from the nested folder to the root, and then removes the &lt;code&gt;temp&lt;/code&gt; folder and its contents (the empty &lt;code&gt;out&lt;/code&gt; folder). The dot (&lt;code&gt;.&lt;/code&gt;) represents the current working directory (where the repo was checked out on the VM).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config user.name github-actions

    git config user.email github-actions@github.com
    git add &lt;span class="nb"&gt;.&lt;/span&gt;
    git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"unzip"&lt;/span&gt;
    git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part of the script pushes the changes back to the repository.&lt;/p&gt;

&lt;p&gt;Image: Detail from The Unarchiver zip file icon. I looked for an appropriate unzip image with a Creative Commons license, but the results were not safe for work.&lt;/p&gt;

</description>
      <category>git</category>
      <category>automation</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Getting started in developer relations</title>
      <dc:creator>Andrew Owen</dc:creator>
      <pubDate>Mon, 19 Dec 2022 18:17:42 +0000</pubDate>
      <link>https://dev.to/aowendev/getting-started-in-developer-relations-44mb</link>
      <guid>https://dev.to/aowendev/getting-started-in-developer-relations-44mb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article originally appeared on my personal dev blog: &lt;a href="https://andrewowen.net/blog/" rel="noopener noreferrer"&gt;Byte High, No Limit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It may surprise you that the field of developer relations has been around for nearly 40 years at the time of writing. It started at Apple with Mike Boich and Guy Kawasaki on the Macintosh project. But it didn't go mainstream until nearly 30 years later.&lt;/p&gt;

&lt;p&gt;Apple had helped to define the personal computer market with the Apple II. Its killer app was VisiCalc, the first microcomputer spreadsheet. IBM watched as businesses rapidly adopted the Apple II, and its response in 1981 was the IBM PC. By the time the Mac launched in 1984, the IBM PC accounted for around 80 per cent of the PC market.&lt;/p&gt;

&lt;p&gt;The Macintosh wasn't compatible with software written for the Apple II or the IBM PC, and if it was going to be a success, it needed software. So the role of software evangelist was created at Apple to promote the Mac as a development platform. And it worked, because unlike other IBM PC alternatives that launched in the 1980s, the Mac is still around.&lt;/p&gt;

&lt;p&gt;In this age of disruption, it's widely recognized that if you want to get developers to use your products, what you need to offer them is the fastest route to developer success. The once popular Atari ST and Commodore Amiga are now remembered mainly as games machines, if they are remembered at all. You don't want your product to go the same way.&lt;/p&gt;

&lt;p&gt;When I was getting started as a developer advocate, I reached out to someone I'd met at an API conference for advice. They gave me three things they wished they'd heard when they were starting out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Recognize that you're wearing, at minimum, two hats. Your developer hat, which should feel familiar-ish, because now you're not just a developer. You're also in Product/Marketing/Engineering/Sales, whichever org DevRel sits within at your company. When you accept that you have multiple roles, it will be easier to prioritize the different goals.&lt;/li&gt;
&lt;li&gt;Protect your developer time. People will want a lot more face time with you. You can give them face time, up to a limit. If all your time goes towards stakeholder meetings with other Product Managers, you will not be writing code. You need to keep writing some code, to maintain your credibility.&lt;/li&gt;
&lt;li&gt;Your manager should be actively on your side. If not, tell them that's what you need from them for you to do your job successfully. A supportive manager is very helpful overall, I will personally attest.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;He also recommended &lt;a href="https://www.marythengvall.com/devrelbook" rel="noopener noreferrer"&gt;Mary Thengvall&lt;/a&gt;'s book &lt;a href="https://www.bookdepository.com/Business-Value-Developer-Relations-Mary-Thengvall/9781484237472" rel="noopener noreferrer"&gt;“The Business Value of Developer Relations”&lt;/a&gt;. And she recommends Jono Bacon's &lt;a href="https://www.bookdepository.com/Art-Community-2e-Jono-Bacon/9781449312060" rel="noopener noreferrer"&gt;“The Art of Community”&lt;/a&gt;. The former is also a great read if you're planning to set up a DevRel department. The latter should be required reading for all community managers.&lt;/p&gt;

&lt;p&gt;And now here's a list of sites that I've personally found useful in my role as a developer advocate:&lt;/p&gt;

&lt;h4&gt;
  
  
  Blogs
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://thagomizer.com/blog/2017/09/29/we-don-t-do-that-here.html" rel="noopener noreferrer"&gt;Aja Hammerly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/glitch/a-developer-relations-bill-of-rights-21381920e273" rel="noopener noreferrer"&gt;Anil Dash&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ashley-willis.medium.com/what-is-developer-advocacy-3a92442b627c" rel="noopener noreferrer"&gt;Ashley Willis&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Communities
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devrelcollective.fun/" rel="noopener noreferrer"&gt;DevRel Collective&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.devrelx.com/" rel="noopener noreferrer"&gt;DevRelX&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Newsletters
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developeravocados.net/" rel="noopener noreferrer"&gt;Developer Avocados Weekly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://devrelweekly.com/" rel="noopener noreferrer"&gt;DevRel Weekly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Podcasts
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://anchor.fm/api-the-docs-podcast/" rel="noopener noreferrer"&gt;API The Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Websites
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developerrelations.com/" rel="noopener noreferrer"&gt;DeveloperRelations.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devrelresourc.es/" rel="noopener noreferrer"&gt;DevRel Resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  YouTube
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/channel/UCDtYqOjS9Eq9gacLcbMwhhQ" rel="noopener noreferrer"&gt;Adobe Developer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/user/developerworks" rel="noopener noreferrer"&gt;IBM Developer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/channel/UCsMica-v34Irf9KVTh6xx-g" rel="noopener noreferrer"&gt;Microsoft Developer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Image: Original by &lt;a href="http://www.kjokkenutstyr.net/" rel="noopener noreferrer"&gt;Kjokkenutstyr&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>community</category>
      <category>devrel</category>
      <category>devex</category>
      <category>apple</category>
    </item>
  </channel>
</rss>
