Skip to content

1 - Multilingual Hugo Blog

I recently came across a classmate’s sharing, mentioning that after making their blog multilingual, they saw a significant increase in traffic. So, I decided to give it a try as well. With the help of OpenAI, translating articles is not a difficult task. However, adding multilingual support to an existing Hugo site is still not a walk in the park. Although Hugo itself has the basic features for multilingual support (Documentation: Hugo Multilingual), if the chosen theme does not support it, modifications to the theme are necessary.

For my blog, I have chosen the “translation by filename” approach, which seems to be the least intrusive solution based on the documentation. In simple terms, if your blog’s Markdown file is located at /content/blog.md, you can add an blog.en.md file at the same level to provide the English translation. After completion, you can access the translated version by appending /en/ to the domain name. (The default language, in my case, is “Simplified Chinese,” so no language suffix is needed for the path.) However, it is unacceptable to expect users to manually add the language route, so a language selector needs to be added to the page. For now, I have added it to the top of the page. And voila, your multilingual blog is ready to go live!

There are still some known issues that I will address when I have the time:

  1. Various navigations (e.g., the back button in the top left corner) will return to the root directory of the site (i.e., the Simplified Chinese homepage); the proper way is to return to the corresponding homepage of the current language.
  2. The RSS feed link is problematic; the default link provided is still for the primary language, while the English link is under the /en/ path. Perhaps an integrated RSS feed should be considered?

Most of the core multilingual code can be seen in this commit: ca7a83d

For more reference links:

2 - Simulating Wait Times at the Gym

In mid-July, I visited the hospital and started exercising regularly upon the doctor’s advice. After considering my work schedule and my physical condition, I decided to go to the gym for a workout during lunchtime. Currently, my daily exercise routine consists of spending about 20 minutes on the elliptical machine at the gym (while watching an episode of a TV show), burning approximately 320 calories. However, even though there are plenty of elliptical machines in the gym, a situation occasionally arises where there are no available machines and I have to wait for someone to finish. Not knowing how long the wait will be can be slightly anxiety-inducing. So, I started thinking about whether there is a way to quantify the wait time, such as simulating and calculating the probability distribution function. And that’s how this article came about.

Code (mostly written using GPT3.5, with some manual adjustments): link to code

Assumption: There are 10 elliptical machines, and the exercise time of each user on a machine, denoted as t_n, follows a normal distribution with a mean of μ and a standard deviation of σ. When I arrive at the gym, all 10 machines are already occupied, and the remaining exercise time for each user is uniformly distributed between 0 and t_n. The wait time is defined as the minimum of the remaining exercise times for all users.

Considering that the parameters μ and σ cannot be accurately estimated, we consider 9 different scenarios by varying μ as 5/10/15 and σ as 20/25/30. For each scenario, we run 10,000 simulations and calculate the p50/p75/p90/p95 of the wait time. The results are summarized in the following table:

μσmeanp50p75p90p95
2051.6981.2522.4063.8174.811
20101.2580.8581.7722.9443.813
20151.1510.7371.6042.7613.641
2552.1981.6323.1234.9786.229
25101.7691.2582.4944.0775.213
25151.4770.9672.0693.5084.634
3052.6711.9773.8136.0597.515
30102.2841.6693.2195.1866.632
30151.9651.3642.7204.6246.019
Visualization of Simulation Results

Conclusion: Taking all scenarios into account, there is a 50% probability of waiting for about 2 minutes to secure a spot, and in the worst-case scenario, there is a 90% probability of waiting for up to 6 minutes to secure a spot.

3 - Migrating from Disqus to Giscus for the Comment System

I used to use Disqus for the comment system, but there were occasional issues with accessing it from within China, and new users had to register again. Considering that most of the readers of this site are likely GitHub users, migrating to Giscus (a comment system based on GitHub Discussions) seems more suitable. Switching the comment system itself is not difficult, you can refer to this tutorial to modify the Hugo templates and configurations. Migrating the data is not troublesome either, as there weren’t many comments to begin with, so there are only two comments to manually migrate, which shouldn’t take much time (although I also tried an automated solution, but it seems to have some issues and the migrated comments don’t show up…). The slightly annoying part is actually the switching between light/dark mode in Giscus.

Because this blog has its own toggle button (see previous post), when users visit, they may retrieve the color mode preference from localstorage. However, currently, when Giscus is loaded, Hugo writes the color preference parameters into the HTML source code during site generation. Therefore, when the user clicks the button to switch, the color preference in Giscus needs to be switched as well. Referring to this issue in the official documentation, this feature is not difficult to implement. However, there is still a problem because after Giscus is loaded, if the user clicks the button to switch color mode preference, Giscus will use the color preference based on my Hugo configuration file, rather than what is stored in the user’s localstorage. As a result, the user may manually choose the light mode, but if the browser settings have prefer-color-scheme: dark, Giscus will display a black background with white text. There wasn’t a good solution to this problem in the previous issue discussions, although I saw someone using setTimeout to continuously loop, but that doesn’t seem very elegant. After reading the official documentation, I found that Giscus actually sends an event to the parent window after it finishes loading, so as long as we listen for this event, we can set the color preference for Giscus after it has loaded. You can refer to this commit for the related implementation.

Perhaps there will be more comments after the switch? Hopefully.

4 - Implementing a Simple Dark Mode with CSS Filter

Easier than imagined

The Manis theme used on this blog does not provide native support for dark mode, so I decided to add it myself. Initially, I thought about customizing the CSS with media queries, but it seemed like a bigger change. I did a quick search and found that someone had already proposed the idea of using CSS Filter to implement a simple dark mode, and there was even code that could be directly applied to a Hugo blog. Compared to media queries, using CSS Filter not only simplifies the implementation, but also allows users to switch between light and dark mode without adjusting the system/browser’s global settings.

In the specific CSS implementation, I first used invert(1) to invert the colors of the entire webpage, but this also caused a reversal of color tones. Therefore, I used hue-rotate(180deg) to bring the tones back. However, while this operation is suitable for text, it affects the display of images, videos, and other elements, making them appear as if they were being X-rayed. So, I had to apply invert(1) hue-rotate(180deg) again to these elements that needed to be excluded from the dark mode, in order to revert them back to normal.

html {
    background-color: #ebebeb !important;
}

html {
filter: invert(100%) hue-rotate(180deg);
}

/* using not to exclude certain elements */
img:not(.icon-text, .icon-social),
video,
code {
filter: invert(100%) hue-rotate(180deg) contrast(100%);
}

To allow users to switch between light and dark mode, an additional toggle icon needs to be introduced. When clicked, it will insert/remove the CSS tag for dark mode and save the user’s preference to localStorage. If the user has not explicitly set a preference, the system/browser’s global dark mode setting should be followed. Therefore, I used window.matchMedia to detect it.

var toggle = document.getElementById("dark-mode-toggle");
var darkTheme = document.getElementById("dark-mode-theme");

// probe system default dark mode setting
var systemDefault = null
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
    systemDefault = "dark";
} else {
    systemDefault = "light";
}

// use user preference if possible
var savedTheme = localStorage.getItem("dark-mode-storage") || systemDefault;
setTheme(savedTheme);

toggle.addEventListener("click", () => {
    if (toggle.src.endsWith("/img/moon.svg") ) {
        setTheme("dark");
    } else if (toggle.src.endsWith("/img/sun.svg") ) {
        setTheme("light");
    }

});

function setTheme(mode) {
    localStorage.setItem("dark-mode-storage", mode);

    if (mode === "dark") {
        darkTheme.disabled = false;
        toggle.src = "/img/sun.svg";
    } else if (mode === "light") {
        darkTheme.disabled = true;
        toggle.src = "/img/moon.svg";
    }
} 

The complete modifications can be seen in my Pull Request for implementing dark mode in this theme. A simple example can be found in this Gist. The final result is as follows:

Demo

Update on 12/27: The PR has been accepted and merged.

5 - Connecting to Physical Screen with VNC

When searching for “Linux remote desktop,” most tutorials suggest using the combination of “xrdp + xfce4.” In general, this combination works well, but it may not be as effective for certain special requirements. In my use case, there are times when I have a long-running task on a Linux workstation in the lab, and I need to check if the process is running smoothly when I return to my dormitory. If it were a regular CLI program, using terminal multiplexers like “screen” or “tmux” would be more than sufficient. However, I am using a GUI program. So, I searched for a solution and found that it is possible to connect to an ongoing X session using VNC, similar to tools like TeamViewer. Here are the specific steps:

  1. Install the TigerVNC server.
  2. Run “vncpasswd” to create a VNC password.
  3. Start the TigerVNC service.
  4. Use “x0vncserver” to open a VNC session connected to Display 0.
     x0vncserver -display :0 -PasswordFile=/home/{username}/.vnc/passwd 
  5. Connect using a VNC client on another device.

In my own experience on Windows, it seems that RealVNC Viewer provides a better user experience compared to TigerVNC Viewer.

Another issue that may affect the user experience is scaling and screen resolution. The lab workstation has a 4K screen with 200% scaling, and when connecting from a 1080p laptop, the text may appear too small. TigerVNC seems to have an auto-scaling feature, but it doesn’t seem to work when connecting the VNC session to a physical screen. My workaround is to first connect and then manually adjust the resolution settings on the remote system (usually setting it to 2560x1440 is sufficient), then restart x0vncserver and reconnect. Although it’s a bit cumbersome, at least it solves the problem of usability.

6 - Constructing a RegEx that matches all emojis

While researching a CSS custom emoji font issue, I came across a RegEx that can match all emojis (up to the 2018 version) and it also provides corresponding test examples. You can find it here: Regex to match all emoji - Regex Tester/Debugger

(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])

After reading it, I was skeptical because the RegEx seemed too simple. So, I manually converted it to check the corresponding Unicode codepoint range, and I found that there is indeed a problem. The matching range of this RegEx is too broad, excluding the Copyright and Registered symbols (u+00a9, u+00ae), the remaining ranges are [u+2000, u+3300] and [u+1f000, u+1fbff]. The latter range seems reasonable, as it corresponds to the newly added emoji codepoints according to the Unicode plane mapping on Wikipedia. However, the former range is too extensive, even including Japanese Hiragana and Katakana characters. (Although it does cover almost all emoji codepoints, it misses some corner cases like selectors.)

So, how can we create a RegEx that accurately matches emojis? The idea is simple. First, we need to obtain the Full Emoji List from the Unicode official website, parse all the codepoints that belong to emojis, sort them, and finally merge adjacent codepoints into ranges. However, this is easier said than done. From the perspective of RegEx, characters are in UTF-16 (if using the form \uabcd), so codepoints higher than u+ffff need to be represented using surrogate pairs.

The final result is as follows: (Test link: regex101)

(\u00a9|\u00ae|\u203c|\u2049|\u20e3|\u2122|\u2139|[\u2194-\u2199]|[\u21a9-\u21aa]|[\u231a-\u231b]|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\u24c2|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|[\u2600-\u2604]|\u260e|\u2611|[\u2614-\u2615]|\u2618|\u261d|\u2620|[\u2622-\u2623]|\u2626|\u262a|[\u262e-\u262f]|[\u2638-\u263a]|\u2640|\u2642|[\u2648-\u2653]|[\u265f-\u2660]|\u2663|[\u2665-\u2666]|\u2668|\u267b|[\u267e-\u267f]|[\u2692-\u2697]|\u2699|[\u269b-\u269c]|[\u26a0-\u26a1]|\u26a7|[\u26aa-\u26ab]|[\u26b0-\u26b1]|[\u26bd-\u26be]|[\u26c4-\u26c5]|\u26c8|[\u26ce-\u26cf]|\u26d1|[\u26d3-\u26d4]|[\u26e9-\u26ea]|[\u26f0-\u26f5]|[\u26f7-\u26fa]|\u26fd|\u2702|\u2705|[\u2708-\u270d]|\u270f|\u2712|\u2714|\u2716|\u271d|\u2721|\u2728|[\u2733-\u2734]|\u2744|\u2747|\u274c|\u274e|[\u2753-\u2755]|\u2757|[\u2763-\u2764]|[\u2795-\u2797]|\u27a1|\u27b0|\u27bf|[\u2934-\u2935]|[\u2b05-\u2b07]|[\u2b1b-\u2b1c]|\u2b50|\u2b55|\u3030|\u303d|\u3297|\u3299)|(\ud83c(\udc04|\udccf|[\udd70-\udd71]|[\udd7e-\udd7f]|\udd8e|[\udd91-\udd9a]|[\udde6-\uddff]|[\ude01-\ude02]|\ude1a|\ude2f|[\ude32-\ude3a]|[\ude50-\ude51]|[\udf00-\udf21]|[\udf24-\udf93]|[\udf96-\udf97]|[\udf99-\udf9b]|[\udf9e-\udff0]|[\udff3-\udff5]))|(\ud83d([\udc00-\udcfd]|[\udcff-\udd3d]|[\udd49-\udd4e]|[\udd50-\udd67]|[\udd6f-\udd70]|[\udd73-\udd7a]|\udd87|[\udd8a-\udd8d]|\udd90|[\udd95-\udd96]|[\udda4-\udda5]|\udda8|[\uddb1-\uddb2]|\uddbc|[\uddc2-\uddc4]|[\uddd1-\uddd3]|[\udddc-\uddde]|\udde1|\udde3|\udde8|\uddef|\uddf3|[\uddfa-\ude4f]|[\ude80-\udec5]|[\udecb-\uded2]|[\uded5-\uded7]|[\udedd-\udee5]|\udee9|[\udeeb-\udeec]|\udef0|[\udef3-\udefc]|[\udfe0-\udfeb]))|(\ud83e([\udd0c-\udd3a]|[\udd3c-\udd45]|[\udd47-\uddff]|[\ude70-\ude74]|[\ude78-\ude7c]|[\ude80-\ude86]|[\ude90-\udeac]|[\udeb0-\udeba]|[\udec0-\udec5]|[\uded0-\uded9]|[\udee0-\udee7]))|(\udb40([\udc62-\udc63]|\udc65|\udc67|\udc6c|\udc6e|[\udc73-\udc74]))

The ultimate question: Should you really use regex to handle emojis?

Postscript: After reinventing the wheel, I discovered that someone has already done this work in the emoji-test-regex-pattern repository. Moreover, compared to my single character matching approach, the regex in this repo can match sequences of characters that represent emojis (such as the Chinese flag = flag + China), which is more in line with the specification.

7 - Monitor the number of goroutines

Continuing to complete the previous debug process of writing Raft. After solving the Timer issue, it was discovered that even with repeated testing (using --count 10), the CPU usage continued to increase, although the rate of increase was reduced, it was still quite severe after multiple iterations. There was a preliminary suspicion that when transitioning roles, there might be a mishandling of goroutines leading to goroutine leaks. So, I searched on Stack Overflow and made some modifications to a code snippet that could print the current number of goroutines in the Go Runtime at regular intervals. Finally, it was indeed found that the number of goroutines kept increasing as the tests progressed. After fixing the leak issue (by adding various conditional checks), each time the test restarted, the number of goroutines would decrease to a level similar to the initial state, and the resource consumption during multiple tests also returned to normal.

const Debug = 0

var DebugShowGoroutineCount uint32

func showGoroutineCount() {
	beginTime := time.Now()
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			fmt.Println("time:", time.Since(beginTime), "goroutine count:", runtime.NumGoroutine())
		}
	}
}

func runShowGoroutineCount() {
	if atomic.CompareAndSwapUint32(&DebugShowGoroutineCount, 0, 1) {
		go showGoroutineCount()
	}
}

// to start, just invoke `runShowGoroutineCount` at anywhere you like, perhaps at `ServerStart` or something like that.

8 - Code blocks within Markdown tables

Markdown provides support for tables, but only basic text formatting (such as bold, italics, inline code, etc.) is supported within table cells. More complex text formatting, such as code blocks and horizontal lines, is not supported. If you need to add support for complex formatting within tables and you are using Github Flavored Markdown, one approach is to use HTML to define the table structure and then include inline Markdown text within it. Here is an example:

Please note the following:

  • The <td> and </td> tags for the corresponding table cells should be placed at the beginning of a new line (without any indentation).
  • There should be a blank line between the Markdown text inside the table cell and the <td> and </td> tags.

Example:

Column 1Column 2
Code Block
print("hello world")
Horizontal Line

Markdown


Some Text

Code: Note that the code block should end with 3 tildes (I’m using two here because three would cause rendering errors and prematurely end the code block).

<table>
<tr>
<td> Column 1 </td> <td> Column 2 </td>
</tr>
<tr>
<td> Code Block </td>
<td>                           <!--line start-->
                               <!--empty line-->
```python
print("hello world")
``                             <!--Should be 3 tilt here!-->
                               <!--empty line-->
</td>                          <!--line start-->
</tr>
<tr>
<td> Horizontal Line </td>
<td>

**Markdown** 

---

Some Text

</td>
</tr>
</table>

Reference link:

9 - Integrating Disqus with Hugo

Hugo has built-in support for Disqus. In theory, you only need to set the disqusShortname attribute at the top level of your site’s config.toml file. However, there are some pitfalls when actually using it. Here are the specific steps.

  1. Register your own account on the Disqus website.
  2. After logging in to the Disqus website, go to Settings in the upper right corner, then select Moderation on the left side. Here, create a new site, and the site name ({site_name}.disqus.com) should be used as the value for disqusShortname.
  3. Once the site is created, choose the Free Plan on the Billing page.
  4. Set the disqusShortname in the config.toml file of Hugo.

Other minor issues:

  • No comments displayed locally: In the theme’s disqus.html file (located at {site_folder}\themes\{theme_name}\layouts\partials\disqus.html), the comment box will not load when the condition (indow.location.hostname == "localhost") is met. If you need to debug, you can add a comment to this condition, and it will display normally locally.