Multilingualism of web service can be quite a challenge. I wanted to implement initial draft of multilingualism without major changes in generator. I have decided that content list subpage will be first to receive this feature.
Idea
Webpage should have language selection menu, showed after clicking on language selection button. Button should have form of a flag, that will match currently selected language. Button should be localised somewhere in header. I have prepared sketch drawing, shown in figure 1.
Assumptions and Design
List of languages will be prepared on fly, using content metadata. List of languages will be converted into foldable html menu.
Implementation
Firstly I prepared draft of html, which I would like to be output of generator. Draft is shown in listing 1. Draft was very helpful while preparing style sheet.
<div class="language menu dropdown">
<div class="language element top">
<span class="language top">Język</span>
</div>
<a href="/la/articles.xhtml">
<div class="language element selector">
<img class="language flag" src="/static/lang/en.svg" />
<span class="language name">English</span>
</div>
</a>
<a href="/la/articles.xhtml">
<div class="language element selector">
<img class="language flag" src="/static/lang/jp.svg" />
<span class="language name">日本語</span>
</div>
</a>
<a href="/la/articles.xhtml">
<div class="language element selector">
<img class="language flag" src="/static/lang/kr.svg" />
<span class="language name">한국어</span>
</div>
</a>
<a disabled="disabled">
<div class="language element selector">
<img class="language flag" src="/static/lang/pl.svg" />
<span class="language name">Polski</span>
</div>
</a>
</div>
Style sheet
Having html draft, I moved to preparing style sheet. First element I want to prepare is language selection button.
I need some national flags, I borrowed some from flag-icons. I have chosen this project, because it is shared by lipis user on MIT license.
I declared button as inline-block
with float
attribute set as
right
.
I might want to have more buttons in header in future.
I tried display: flex
but I given up after half an hour.
.language.menu {
display: inline-block;
float: right;
}
.language.menu .button {
padding: 5px;
width: 20px;
height: 20px;
background-color: #8c8f94;
}
.language.flag {
width: 16px;
height: 16px;
max-height: 100%;
max-width: 100%;
margin: 2px;
}
I define menu-width
, menu-max-height
global constants.
I decided on menu width as 200px for large screen, and full screen
for mobile.
:root {
...
--menu-width: 100%;
--menu-max-height: 80vh;
}
@media only screen and (min-width: 800px) {
:root {
...
--menu-width: 200px;
}
}
In order to be able to set menu position as absolute
I need to set
body
position as relative
.
Absolute position is relative to the nearest positioned ancestor.
Positioned, that means absolute
or relative
.
body {
...
position: relative;
}
body
postitionI have found several ways to implement foldable menu in css.
I have chosen to change value of opacity
attribute.
I declare dropdown menu as block
with absolute
position, with
intended height and zero width.
I set z-index
as 1, as menu should cover other elements.
I set top and right coordinates to 0.
I set overflow
to hidden
, that way, unfolding animation will
be nicer.
For overflow to work as I want, I need to set white-space
as nowrap
.
I select timefrwame for unfolding animation to 300ms.
Finally I set opacity
to 0.
After hovering button, menu is unfolded.
This is possible because of changing opacity
and width
.
Simple showing the menu, would require only opacity
change.
I wanted to include some animation, and decided that changing width
looks best.
.language.menu.dropdown {
display: block;
position: absolute;
width: 0px;
height: fit-content;
max-height: var(--menu-max-height);
z-index: 1;
background-color: #c3c4c7;
top: 0;
right: 0;
opacity: 0;
transition: width 300ms;
overflow: hidden;
white-space: nowrap;
}
.language.menu:hover ~ .language.menu.dropdown,
.language.menu.dropdown:hover {
width: var(--menu-width);
opacity: 1;
}
Last part was a bit of slippery slope. I seriously doubt it could not have been done simpler or better. I wanted to highlight menu element that is currently selected. In the same time, I did not wanted to highlight elements that are marked inactive. In addition to this, I wanted to dim inactive element a bit. What I ended up is following semantic monster, but it works!
.language.menu.dropdown a[disabled="disabled"] .selector {
color: #8c8f94;
}
.language.menu.dropdown a:not([disabled="disabled"]) .selector:hover {
background-color: #f0f0f1;
}
Generator
Extending generator with dropdown menu was pretty simple task. Firstly, I reworked my html draft into template shown in listing 7.
language_menu_entry_template = \
"""
<a {link}>
<div class="language element selector">
<img class="language flag" src="/static/lang/{language}.svg" />
<span class="language name">{language_name}</span>
</div>
</a>
"""
I generated buttons for all available languages. Currently selected language was added as well, but as inactive button.
def render_list(content_type, path, content_dict, template, language, languages):
'''Render list of content basing on template'''
...
language_menu_entries = ""
for selected in sorted(languages):
link = f"href=\"/{selected}/{content_type}s.xhtml\""
if selected == language:
link = "disabled=\"disabled\""
language_menu_entries += language_menu_entry_template.format(
link=link,
language=selected,
language_name=language_name[selected]
)
name = content_type_name_plural[content_type][language]
Entire implementation can be found in commit 0d610216.
Effect
Final effect is shown in wideo 1. Animation looks smooth, without any visible artifacts, which was not an easy task. Language selection is easy and intuitive, I hope at least. Square button fits rest of the page layout well.
Summary
Adding multilingualism to list subpage was not a difficult task. All elements required for implementation were ready. Only CSS was a bit of pain. But I know well that it was an easy part. Multilingualism of content page will not be as easy. For now I did not designed any link between multiple translations of the same content. So I need to extend somehow metadata of content first.