Compare commits
10 commits
f080fefa6e
...
067eddd912
Author | SHA1 | Date | |
---|---|---|---|
067eddd912 | |||
49c95d212c | |||
8cfc8b17d0 | |||
8ac7064d37 | |||
3a92f53133 | |||
e16abc7bff | |||
b5237c2f5d | |||
f231c3da8e | |||
ea6e89b1d3 | |||
29789da766 |
10 changed files with 300 additions and 7 deletions
|
@ -2,7 +2,8 @@ baseurl: "https://rennerocha.com/"
|
||||||
languageCode: "pt-br"
|
languageCode: "pt-br"
|
||||||
title: "Renne Rocha"
|
title: "Renne Rocha"
|
||||||
theme: "hugo-tania"
|
theme: "hugo-tania"
|
||||||
paginate: 6
|
pagination:
|
||||||
|
pagerSize: 6
|
||||||
|
|
||||||
params:
|
params:
|
||||||
author: "Renne Rocha"
|
author: "Renne Rocha"
|
||||||
|
@ -25,8 +26,10 @@ params:
|
||||||
|
|
||||||
# Comments settings
|
# Comments settings
|
||||||
comments:
|
comments:
|
||||||
enabled: false
|
enabled: true
|
||||||
provider: giscus
|
provider: mastodon
|
||||||
|
commentsEmail: "blog@rennerocha.com"
|
||||||
|
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
header:
|
header:
|
||||||
|
|
|
@ -23,6 +23,17 @@ I am the maintainer of the following open source projects:
|
||||||
|
|
||||||
- [DojoPuzzles](https://dojopuzzles.com) is a page that helps participants of [Coding Dojo](https://codingdojo.org/practices/WhatIsCodingDojo/) sessions to choose a suitable problem to be solved. I created this project many years ago, when I organized these sessions. There are not many updates since them, but it is still very used in Brazilian community.
|
- [DojoPuzzles](https://dojopuzzles.com) is a page that helps participants of [Coding Dojo](https://codingdojo.org/practices/WhatIsCodingDojo/) sessions to choose a suitable problem to be solved. I created this project many years ago, when I organized these sessions. There are not many updates since them, but it is still very used in Brazilian community.
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
I have plans to attend the following events in the coming months. If you want to invite me for a beer, I might accept the invitation 🙂
|
||||||
|
|
||||||
|
| | When | Where | What |
|
||||||
|
| - | - | - | - |
|
||||||
|
|  | May, 16-17 | _São Paulo-SP, Brazil_ | [CryptoRave 2025](https://2025.cryptorave.org/) |
|
||||||
|
|  | May, 17-18 | _São Paulo-SP, Brazil_ | [Security BSides](https://securitybsides.com.br/2025/) |
|
||||||
|
|  | June, 17-22 | _Ribeirão Preto-SP, Brazil_ | [Caipyra 2025](https://2025.caipyra.python.org.br/) |
|
||||||
|
|  | October, 21-27 | _São Paulo-SP, Brazil_ | [Python Brasil 2025](https://2025.pythonbrasil.org.br/) |
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
You can find me on [Linkedin](https://www.linkedin.com/in/rennerocha/) or the [Fediverse](https://chaos.social/@rennerocha).
|
You can find me on [Linkedin](https://www.linkedin.com/in/rennerocha/) or on my Fediverse accounts: [@rennerocha@chaos.social](https://chaos.social/@rennerocha) (English mainly) and [@renne@rocha.social](https://go.rocha.social/@renne/) (Portuguese mainly).
|
||||||
|
|
|
@ -3,6 +3,8 @@ title: "Configuring a self-hosted calendar for your small community"
|
||||||
date: 2024-02-08
|
date: 2024-02-08
|
||||||
tags: ["gancio", "fediverse", "calendar", "self-host"]
|
tags: ["gancio", "fediverse", "calendar", "self-host"]
|
||||||
slug: configuring-self-hosted-calendar-for-small-community
|
slug: configuring-self-hosted-calendar-for-small-community
|
||||||
|
params:
|
||||||
|
mastodon_id: 114503452745400601
|
||||||
---
|
---
|
||||||
|
|
||||||
I am one of the co-founders of [Laboratório Hacker de Campinas](https://lhc.net.br) (LHC), one of the first hackerspaces in Brazil and during more than a decade we always struggled how to publish and publicize our events.
|
I am one of the co-founders of [Laboratório Hacker de Campinas](https://lhc.net.br) (LHC), one of the first hackerspaces in Brazil and during more than a decade we always struggled how to publish and publicize our events.
|
||||||
|
@ -15,6 +17,8 @@ I was always looking for some kind of open source and (possibly) self-hosted sol
|
||||||
|
|
||||||
To deploy it, I decided to use [fly.io](https://fly.io), a Platform-as-a-Service (PaaS) company that with one `Dockerfile` and a few configuration files I was able to quickly deploy [Gancio](https://gancio.org/) there. They require a credit card to create an account, but if your application doesn't use much CPU|memory|network|storage (which is our case with our calendar), it is very likely that you will never reach the minimum threshold to start paying. So it will be basically free to host it.
|
To deploy it, I decided to use [fly.io](https://fly.io), a Platform-as-a-Service (PaaS) company that with one `Dockerfile` and a few configuration files I was able to quickly deploy [Gancio](https://gancio.org/) there. They require a credit card to create an account, but if your application doesn't use much CPU|memory|network|storage (which is our case with our calendar), it is very likely that you will never reach the minimum threshold to start paying. So it will be basically free to host it.
|
||||||
|
|
||||||
|
**UPDATE (2025-03-04): fly.io did some changes in how they charge their users since this post was written (and to be honest, I still didn't completely understand how it works), so perhaps following the steps that I shared here will not be totally free as before. But due to the ease of configuration and maintenance, I still believe it is a low-cost alternative to be considered.**
|
||||||
|
|
||||||
**Disclaimer** I am not being sponsored by [fly.io](https://fly.io). It is just a service that worked well for my needs.
|
**Disclaimer** I am not being sponsored by [fly.io](https://fly.io). It is just a service that worked well for my needs.
|
||||||
|
|
||||||

|

|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
---
|
||||||
|
title: "Things I learned from translating free software projects"
|
||||||
|
date: 2025-03-31
|
||||||
|
tags: ["free software", "translation", "i18n"]
|
||||||
|
slug: things-i-learned-from-translating-free-software-projects
|
||||||
|
params:
|
||||||
|
mastodon_id: 114502010539217500
|
||||||
|
---
|
||||||
|
|
||||||
|
Everyone who wants to start contributing to free software projects struggles to know how to start. There are different paths you can follow to get involved in a community. Smaller projects are easier to start with, but some will require technical knowledge that you don't have yet. Other projects may be more welcoming to new contributors but will require you to learn how the community works and its culture.
|
||||||
|
|
||||||
|
As a non-native English speaker, I found that one way to contribute is by translating applications **I use** into Brazilian Portuguese.
|
||||||
|
|
||||||
|
The first project I got involved with in translation was [Gancio](https://gancio.org/), a shared agenda for local communities. It had several translations, but the Portuguese one wasn't good — too many untranslated strings, and the translated ones weren't consistent. As I was installing it to be used by the community of my [hackerspace](https://lhc.net.br/) in Brazil, having it in Portuguese was very important to help people who don't know English use it entirely.
|
||||||
|
|
||||||
|
Some months later, I installed [pretix](https://pretix.eu/about/en/), a ticketing software I was going to use for a conference I was organizing. This project was in very bad shape, with only 18% translated into Portuguese, and the existing translations were terrible (with some very serious mistakes). Now, it is 95% translated, with almost everything reviewed.
|
||||||
|
|
||||||
|
After working on these projects, I learned a few things that I believe are important to mention if you plan to start following this path to contribute to free software.
|
||||||
|
|
||||||
|
This is not a full list of recommendations, techniques, and processes for translating free software. The two projects I worked on didn't have a group of people dedicated to translating them into Portuguese, but for bigger projects (like Python documentation, for example), we will usually find a more organized group of people with more well-defined processes, communication channels, and discussions to decide which is the best translation to use.
|
||||||
|
|
||||||
|
##### You will learn the business of the software
|
||||||
|
|
||||||
|
When you start translating software, you probably know a bit about its business concepts. But during the process, you will come across sentences and words that will lead you to discover other features you probably didn't know existed or didn't realize were so complete.
|
||||||
|
|
||||||
|
After working on these projects, I felt much more confident to start contributing with code (and I did for both projects). So, the translation task allowed me to contribute code as well.
|
||||||
|
|
||||||
|
##### Be aware of regional differences
|
||||||
|
|
||||||
|
Brazilian Portuguese is different from Portugal (or European) Portuguese. Even though we can understand both quite easily, software translated into one variant will look 'weird' to a native speaker of the other. This is also true for other languages, so you need to consider this when translating.
|
||||||
|
|
||||||
|
When I was working on Gancio, there was only `Portuguese` as a language choice. One day, I noticed that another person started to contribute and re-translate lots of words and expressions from the Brazilian variant to the Portugal one. **Don't do that!** You will be throwing someone else's work into the trash.
|
||||||
|
|
||||||
|
I didn't want to start a language war, so I asked the project's maintainer to create two separate versions: `pt-br` (for Brazilian Portuguese) and `pt-pt` (for Portugal Portuguese), so we could continue translating using the terms that make more sense to us. This is probably true for other languages (Spanish came to mind).
|
||||||
|
|
||||||
|
##### Create a glossary
|
||||||
|
|
||||||
|
Keep your translation consistent. Even if there are many different words for a term, avoid using synonyms everywhere. When you find a term that is used in many places, add it to a glossary so other translators will know that they should use one form rather than another
|
||||||
|
|
||||||
|
For example, in Portuguese, the verb `to delete` can be translated as `apagar`, `excluir`, or `deletar`. All of them are valid translations, but it is preferable to stick with one. This will make it easier for new translators to decide which expression/word to use, and for final users, the UI will look much better and be easier to use.
|
||||||
|
|
||||||
|
##### Know your own language
|
||||||
|
|
||||||
|
It is acceptable to make some mistakes during your translation. Usually, we are not language experts and/or professional translators. But we need to be very careful not to commit gross mistakes. Correct spelling and verb conjugations are essential. If you are not sure about how to spell a word, check it in a dictionary beforehand. Even if you have 1% doubt about whether that is the correct spelling, validate it.
|
||||||
|
|
||||||
|
##### Short words in English may not be short in other languages
|
||||||
|
|
||||||
|
Some words in English that are short can be longer in other languages. For example, `Next` is `Próximo` in Portuguese (3 extra characters). Sometimes this is not relevant, but some UIs have strict layout limits (e.g., the width of a button may be fixed), and it will not look good with longer translations. When you find something like this, checking how the translation will look is important.
|
||||||
|
|
||||||
|
Sometimes you will be able to choose a synonym in your language that keeps the same meaning; other times, you may suggest changes in the project code so the UI is not broken.
|
||||||
|
|
||||||
|
This [issue in pretix](https://github.com/pretix/pretix/issues/4796) is a great example of this.
|
||||||
|
|
||||||
|
##### Inclusive language is important
|
||||||
|
|
||||||
|
Inclusive language is a way of speaking that aims to avoid expressions that may be sexist, racist, or biased against certain groups of people. I noticed during the translations that sometimes it is difficult to translate some neutral terms in English into Portuguese.
|
||||||
|
|
||||||
|
Many nouns in English are not gender-specific, so if you say `the reviewer`, you are not specifying the gender of the person reviewing a proposal. But in Portuguese, this noun can be translated as `o revisor` (male) or `a revisora` (female). `All` can be `todos` (male) or `todas` (female). So, you can see how hard this can be.
|
||||||
|
|
||||||
|
The common 'default' is to use the male variant, but I tried my best to avoid it. There are some constructions that people use (in Portuguese, but I know that every language has constructions like this) to remove that bias. For example, instead of `todos` and `todas`, some people use `todes` (the `e` is added instead of using `o` and `a`).
|
||||||
|
|
||||||
|
This is not a common way to write Portuguese, and I personally don't like it (but it is not wrong if you decide to use it). So, some possibilities can be:
|
||||||
|
|
||||||
|
- Remove the article before the noun. For example, when translating `the speaker`, instead of using `o palestrante` or `a palestrante`, use only `palestrante`.
|
||||||
|
- Choose a different word that has the same meaning.
|
||||||
|
- Write the sentence in a different way, keeping the meaning but avoiding gender markers.
|
||||||
|
|
||||||
|
It is not an easy task, but it is important to worry about that. Remember that the software you are translating will be used by people worldwide. **Be respectful to them!**
|
||||||
|
|
||||||
|
##### You may procrastinate a lot doing this
|
||||||
|
|
||||||
|
Some days, I was tired and didn't want to do anything else. But I felt guilty for not doing anything, so I spent a significant amount of time translating the projects. I started to consider this a form of procrastination (at least the result of my procrastination was something meaningful), but — at least for me — it was important to set some boundaries and limit my time spent doing translations. I limited myself to 30 minutes every business day. It worked well for me.
|
|
@ -0,0 +1,91 @@
|
||||||
|
---
|
||||||
|
title: "Creating backups for fly.io Volumes"
|
||||||
|
date: 2025-04-30
|
||||||
|
lastmod: 2025-05-06
|
||||||
|
tags: ["self-host", "fly.io", "backup"]
|
||||||
|
slug: creating-backups-for-fly-io-volumes
|
||||||
|
---
|
||||||
|
|
||||||
|
I have a few applications running on [fly.io](https://fly.io), and some of them need to
|
||||||
|
keep data in the file system persistently (more precisely, an SQLite database file and
|
||||||
|
user-submitted data) so that it is not lost after a redeploy or when the Fly Machine running my application is
|
||||||
|
restarted.
|
||||||
|
|
||||||
|
To achieve that, I use [Fly Volumes](https://fly.io/docs/volumes/overview/) which are local
|
||||||
|
persistent storage for Fly Machines, mounted in my server just like a regular directory. This setup works fine,
|
||||||
|
but I began considering how to back up the data stored there.
|
||||||
|
|
||||||
|
[Volume snapshots](https://fly.io/docs/volumes/snapshots/) are created automatically on a daily basis and
|
||||||
|
retained for 5 days by default. However, there doesn't seem to be an easy (or well-documented) way to implement
|
||||||
|
a custom backup policy. I wanted the ability to copy the entire directory's content using tools like `rsync` or upload
|
||||||
|
it to an S3 bucket on my own schedule.
|
||||||
|
|
||||||
|
I explored solutions involving `cron` jobs running inside my Fly Machine, but they became overly complicated.
|
||||||
|
These approaches required modifying my `Dockerfile` to install additional applications, and I wasn't sure
|
||||||
|
how to manage the schedule effectively, especially since I configured my machines to auto-stop to save resources.
|
||||||
|
|
||||||
|
Direct SSH connections requires me to use `flyctl` CLI and it wasn't clear to me how to handle authentication
|
||||||
|
in this case. After some research, I found that I can use [access tokens](https://fly.io/docs/security/tokens/)
|
||||||
|
to connect to the machines using SSH allowing me to send commands there in an automated way.
|
||||||
|
|
||||||
|
## Generating your access token
|
||||||
|
|
||||||
|
First step is to create an access token that allows me to send commands to my machine without requiring any
|
||||||
|
manual form of authentication. This can be done using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fly tokens create ssh -n my-token-name
|
||||||
|
```
|
||||||
|
|
||||||
|
Check the [command documentation](https://fly.io/docs/flyctl/tokens-create-ssh/) for more options. The output of
|
||||||
|
this command will be as the following, where `<TOKEN_CONTENT_STRING>` will be a very long string that
|
||||||
|
you need to store and don't share it publicly.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FlyV1 <TOKEN_CONTENT_STRING>
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the token to an environment var in the machine you will run the backup script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export FLY_SSH_TOKEN=<TOKEN_CONTENT_STRING>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data location
|
||||||
|
|
||||||
|
The volume is mounted in `/data` directory, defined in our application `fly.toml` file:
|
||||||
|
|
||||||
|
```
|
||||||
|
[[mounts]]
|
||||||
|
source = 'app_data'
|
||||||
|
destination = '/data'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creating a backup script
|
||||||
|
|
||||||
|
Given that we have a token, we are now able to execute SSH commands remotely on our Fly Machine. In
|
||||||
|
our scenario, I am compacting the whole content of `/data/` directory (where all the data that I want
|
||||||
|
to backup is located) generating a tarball, then I download it locally.
|
||||||
|
|
||||||
|
I could create a custom script and copy it to the remote machine if I want to perform more complex
|
||||||
|
tasks, or you can extend/modify this script to perform other tasks (e.g. download the tarball
|
||||||
|
and upload to a S3 bucket).
|
||||||
|
|
||||||
|
```
|
||||||
|
#!/bin/bash
|
||||||
|
# backup.sh
|
||||||
|
|
||||||
|
# Need to start container in fly.io if it was stopped by inactivity
|
||||||
|
curl -s -o /dev/null https://your-app.fly.dev/
|
||||||
|
|
||||||
|
filename="data_backup_$(date +%F).tar.gz"
|
||||||
|
fly ssh console -C 'tar cvz /data' -t $FLY_SSH_TOKEN > $filename
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run it periodically
|
||||||
|
|
||||||
|
Now you can add `backup.sh` to your `crontab` schedule, or even adapt the procedure described here
|
||||||
|
to be executed in other environments, like defining a GitHub Action or another way to schedule jobs.
|
||||||
|
|
||||||
|
I know this is not the most complete way to implement a backup policy, but it is working for my current projects.
|
||||||
|
In the future, as I improve my scripts, I will possibly update this post to make it more complete.
|
112
layouts/partials/comments/include.html
Normal file
112
layouts/partials/comments/include.html
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
{{ if .Site.Params.comments.enabled }}
|
||||||
|
|
||||||
|
<section id="email-comments">
|
||||||
|
<p>If you want to start a discussion about this topic, you can send me an <strong><a href="mailto:{{ $.Site.Params.comments.commentsEmail }}?subject={{ replace (printf "Re: %s" $.Page.Title) "\"" "'" }}">e-mail: <i>blog@rennerocha.com</i></a></strong> ✉️</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{ if .Params.mastodon_id}}
|
||||||
|
<section id="mastodon-comments">
|
||||||
|
<h4>Comments</h4>
|
||||||
|
<div class="comment-ingress">
|
||||||
|
Comment by replying to <strong><a href="https://chaos.social/@rennerocha/{{ .Params.mastodon_id }}">this post on Mastodon</a></strong>.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="comments" data-id="{{ .Params.mastodon_id }}">
|
||||||
|
<p>Loading comments...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="continue-discussion">
|
||||||
|
<p>Continue discussion on <a href="https://chaos.social/@rennerocha/{{ .Params.mastodon_id }}">Fediverse</a> »</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<template id="comment-template">
|
||||||
|
<hr />
|
||||||
|
<article class="blog-comment" style="display: grid; grid-template-columns: 10% 90%;">
|
||||||
|
<div>
|
||||||
|
<img />
|
||||||
|
</div>
|
||||||
|
<div class="comment-content">
|
||||||
|
<div class="author" style="font-size: smaller; font-weight: bold"></div>
|
||||||
|
<div class="publish-date" style="font-size: smaller; font-weight: lighter; font-style: italic;"></div>
|
||||||
|
<div class="comment" style="font-size: larger; font-weight: bold"></div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function renderComment(comment, target, parentId) {
|
||||||
|
const node = document
|
||||||
|
.querySelector("template#comment-template")
|
||||||
|
.content.cloneNode(true);
|
||||||
|
|
||||||
|
const author = node.querySelector(".author");
|
||||||
|
|
||||||
|
let mastodonAcct = comment.account.acct;
|
||||||
|
if (mastodonAcct === "rennerocha") {
|
||||||
|
mastodonAcct = "rennerocha@chaos.social";
|
||||||
|
}
|
||||||
|
|
||||||
|
let in_reply_to_id = comment.in_reply_to_id;
|
||||||
|
|
||||||
|
author.innerHTML = `<a href="${comment.account.url}" target=_blank>${comment.account.display_name} (${mastodonAcct})</a>`;
|
||||||
|
|
||||||
|
const commentContainer = node.querySelector(".blog-comment");
|
||||||
|
if (in_reply_to_id !== parentId) {
|
||||||
|
commentContainer.classList.add("indent");
|
||||||
|
}
|
||||||
|
|
||||||
|
const publishDate = node.querySelector(".publish-date");
|
||||||
|
const dateObj = new Date(comment.created_at);
|
||||||
|
|
||||||
|
const dateTime = `${dateObj.getDate()}.${
|
||||||
|
dateObj.getMonth() + 1
|
||||||
|
}.${dateObj.getFullYear()} ${dateObj.getHours()}:${dateObj.getMinutes()}`;
|
||||||
|
|
||||||
|
publishDate.innerHTML = `<a href="${comment.url}" target=_blank>${dateTime}</a>`;
|
||||||
|
|
||||||
|
const userComment = node.querySelector(".comment");
|
||||||
|
userComment.innerHTML = comment.content;
|
||||||
|
|
||||||
|
const avatar = node.querySelector("img");
|
||||||
|
avatar.src = comment.account.avatar_static;
|
||||||
|
avatar.setAttribute("width", "50");
|
||||||
|
avatar.setAttribute("height", "50");
|
||||||
|
|
||||||
|
target.appendChild(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderComments() {
|
||||||
|
const commentsNode = document.querySelector("#comments");
|
||||||
|
|
||||||
|
const mastodonPostId = commentsNode.dataset?.id;
|
||||||
|
|
||||||
|
if (!mastodonPostId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
commentsNode.innerHTML = "";
|
||||||
|
|
||||||
|
const originalPost = await fetch(
|
||||||
|
`https://chaos.social/api/v1/statuses/${mastodonPostId}`
|
||||||
|
);
|
||||||
|
const originalData = await originalPost.json();
|
||||||
|
renderComment(originalData, commentsNode, null);
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`https://chaos.social/api/v1/statuses/${mastodonPostId}/context`
|
||||||
|
);
|
||||||
|
const data = await response.json();
|
||||||
|
const comments = data.descendants;
|
||||||
|
|
||||||
|
comments.forEach((comment) => {
|
||||||
|
renderComment(comment, commentsNode, mastodonPostId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderComments();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ end }}
|
|
@ -1,2 +1,2 @@
|
||||||
<script async src="https://analytics.umami.is/script.js" data-website-id="f85384d9-9a35-4152-bfbf-2b0779d9194a"></script>
|
<script defer src="https://umami.rocha.dev.br/script.js" data-website-id="474b5248-8cb1-4f43-abd0-585111786004"></script>
|
||||||
<a rel="me" href="https://chaos.social/@rennerocha"> </a>
|
<a rel="me" href="https://chaos.social/@rennerocha"></a>
|
||||||
|
|
BIN
static/ribeirao-preto-flag.png
Normal file
BIN
static/ribeirao-preto-flag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
BIN
static/sao-paulo-flag.png
Normal file
BIN
static/sao-paulo-flag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6 KiB |
|
@ -1 +1 @@
|
||||||
Subproject commit c52acfb01bb0188da4f76b1378d9045041772a99
|
Subproject commit 6c4173f56e24a9192398f94d6e71d3e3da6d52de
|
Loading…
Add table
Add a link
Reference in a new issue