[HN Gopher] Localization in .NET console and desktop apps
       ___________________________________________________________________
        
       Localization in .NET console and desktop apps
        
       Author : svantex
       Score  : 78 points
       Date   : 2024-05-15 10:08 UTC (1 days ago)
        
 (HTM) web link (blog.axantum.com)
 (TXT) w3m dump (blog.axantum.com)
        
       | morning4coffe wrote:
       | Nice blog post! I see you worked with AvaloniaUI. Have you also
       | heard about Uno Platform?
        
         | svantex wrote:
         | Thanks. Yes, I looked at AvaloniaUI, Uno, Xwt, and MAUI before
         | finally deciding on AvaloniaUI. It's not been entirely
         | frictionless, there are some glaring omissions, the
         | documentation is not great and the learning threshold if you're
         | not really good with WPF is pretty high. Still, it does work,
         | and I have the app running on Windows, Linux and macOS with
         | very, very little platform specific code.
        
       | kevingadd wrote:
       | It still frustrates me that there's no built in gettext-style
       | affordances for C#. I guess it doesn't fit the way people
       | typically build software in .NET. IMO the result is that it's
       | more work to localize your software, so a lot of software that
       | should get localized doesn't.
       | 
       | I built a gettext-style system for my own software, so I feel
       | like it's possible to do. But the gap between that and making
       | something everyone can use is pretty big.
        
         | gwervc wrote:
         | It's even more a mess when doing multi-platform. I've a
         | Monogame Android prototype build of my game and somehow the
         | .NET localization throws me exception at runtime. So it's
         | easier to implement a service that load and handle localization
         | than relying on supposed built-in mechanism.
        
         | pjc50 wrote:
         | You're "supposed to" use string resources. There's support for
         | having a different resource pack per languages. It's not
         | exactly frictionless, though.
         | 
         | (With the new "source generator" work it should be relatively
         | straightforward to implement a gettext-style source scan if
         | that's what you want.)
        
           | svantex wrote:
           | There's nothing wrong with the .NET resource system with
           | culture-specific satellite assemblies etc as such. The bigger
           | problem I see is lack of tooling and services for translation
           | by non-developers, volunteers, professional translation
           | bureaus etc. You can't expect them to fire up Visual Studio,
           | clone your repo and start editing resx files. Especially as
           | there's not even a translation view! With .po files, there's
           | a ton of editors, online services, translators etc available.
        
             | radnor wrote:
             | At a previous job we utilized https://webtranslateit.com/ -
             | it organized everything easily for volunteers to provide
             | translations for all resx strings.
        
               | svantex wrote:
               | Nice, but perhaps a little pricy for small startups.
               | That's actually one of the things I didn't emphasize
               | perhaps. Not only is the gettext/.po eco system big,
               | there's also a lot of free or relatively low cost
               | services available.
        
             | ygra wrote:
             | > there's not even a translation view
             | 
             | With Windows Forms, at least back in the day, you could
             | open a Form in the designer, change its language to
             | something else and just edit text on the controls (or
             | images, etc.). The changes would go into a resx file for
             | the selected language. At first glance this looked quite
             | cool, but on the other hand, translators now can ruin your
             | UI, as there's not really a dedicated translation view that
             | only allows to change resources ...
        
       | theolivenbaum wrote:
       | We've been using a library called TNT for this job and are quite
       | happy with the flow: https://github.com/pragmatrix/tnt
       | 
       | You basically annotate all your strings with a "my string".t() or
       | t($"my interpolated string: {var}") and then use their CLI to
       | extract the strings to be translated. It even includes google
       | translation API support for you to kickstart the process.
        
         | svantex wrote:
         | Nice, very ambitious! I like the twist to extract from the
         | compiled IL code, much easier, more stable and reliable than
         | parsing the source. My one gripe here is that the code does not
         | follow the .NET paradigm of using resources at all. Still, very
         | clever and a lot of functionality.
        
       | tracker1 wrote:
       | I've home grown a solution for this a couple times... directories
       | of yaml files, where the default is en-US (for those I've worked
       | on). The main set(s) of yaml files are built to path.filename as
       | a prefix to the internal structure in the file... then typescript
       | type(s) are generated from that... with other files then built
       | and assigned the default type as the type. Then checks for errors
       | are run. From there, code generation for server-side usage is
       | done.
       | 
       | It's tended to work relatively well for me.
        
       | ammo1662 wrote:
       | I remembered the last time I checked, the code page does not even
       | follow the standards (maybe the Shift JIS). Some of the
       | characters are missing in that character set.
       | 
       | Hope they can just use iconv directly.
        
       | svantex wrote:
       | Thanks for all the feedback! The real issue that is not handled
       | by several suggested alternatives is how to actually manage the
       | translation process. resx files as such work fine - but... the
       | problem is getting them from non-technical translators, possibly
       | with their own tool preferences, or volunteers not having any
       | tools or particular tech skills at all. That's what I'm trying to
       | find a better way for, while still not breaking .NET practices.
        
         | tuwtuwtuwtuw wrote:
         | Considering resx has been supported for decades, why not just
         | convert the resx into whatever the translator team wants?
         | 
         | Reading/writing resx is 10 lines of code or something. I just
         | dump ours into an excelsheet with one column per language,
         | because for whatever reason the translators wants it in excel
         | like that. Then when they are done I convert it back to resx
         | files.
        
           | svantex wrote:
           | In my case there's no "translator team" as such. It's a mix
           | of volunteers, part-time paid translators, colleagues etc.
           | I've never had a translator team, it's always various ad-hoc
           | situations and networks of people. And I don't want to write
           | and maintain conversion software and such workflows.
        
         | ComputerGuru wrote:
         | You can see my top-level comment for more info, but we wrote a
         | very quick and dirty UI to manage the XML localizations that
         | allows "forking" a language into a new localization and showing
         | outdated or missing localization strings. I don't think a
         | single person that contributed a translation was actually a
         | developer, but I've had no complaints about it being difficult
         | to follow in the going-on-twenty-years since its release!
         | 
         | https://neosmart.net/forums/threads/translations-to-other-la...
        
           | svantex wrote:
           | Nice - but very special purpose from what I can gather. I've
           | done a few custom solutions myself over the years, but that's
           | exactly what I'm trying to get away from. I want to write
           | code that does cool stuff for the users of my apps, not code
           | that does cool stuff to make translations possible - someone
           | else can do that cool stuff ;-) where I'm the "user".
        
             | ComputerGuru wrote:
             | Thanks. Not actually really special purpose. One-click
             | import from any SWF project, though no easy WPF/XAML sdk
             | for lack of motivation. The GUI is fully app agnostic.
        
         | unsignedint wrote:
         | I'm not sure if I'm missing something here, but many
         | translation solutions support ResX files out of the box. For
         | example, I use memoQ, which has built-in support for ResX
         | files. Additionally, open-source solutions like OmegaT can
         | handle ResX files as well.
        
           | WorldMaker wrote:
           | The author's choice of Loco also claims to directly support
           | ResX: https://localise.biz/help/formats/importing/xml#resx
        
             | svantex wrote:
             | Yes, resx support is finally becoming available in more
             | solutions, but it's still not always a first class citizen.
             | But perhaps that's the long-term solution, wait for resx to
             | take over. Although it still doesn't support plural forms
             | (then again my current setup doesn't either...).
        
               | WorldMaker wrote:
               | gettext/PO is just as deficient at modern plural forms
               | support as ResX, out of the box. The only format that I'm
               | aware of that is specifically built to include it as
               | first-class is Mozilla's Fluent. The same workarounds
               | generally apply to ResX as to PO: create a number of
               | separate strings and do a bit of math up front to choose
               | the right one, or use a formatter on top of PO/ResX that
               | supports something like ICU MessageFormat.
               | 
               | I found an attempt at doing Fluent in .NET:
               | https://github.com/blushingpenguin/Fluent.Net/
               | 
               | I also found at least one library for supporting ICU
               | MessageFormat in .NET:
               | https://github.com/jeffijoe/messageformat.net
        
       | ComputerGuru wrote:
       | We came up with our own localization framework back in the .NET
       | 2.0 days and we're still using it. Version-numbered translations
       | in an XML file with a simple GUI to show missing translations,
       | flag out-dated translations, etc.
       | 
       | The framework and UI: https://github.com/neosmart/Localization
       | 
       | Sample translations: https://github.com/neosmart/easybcd-
       | localization
       | 
       | Guide and screenshots (that should have probably be also folded
       | into the non-existent readme for the localization toolkit repo):
       | https://neosmart.net/forums/threads/translations-to-other-la...
       | 
       | XML is a drag, but JSON isn't supported without a dependency
       | under .NET Framework and the single-binary NLTUI app masks that
       | from most users. XML also gives nice schema validation, for
       | example look at how simple the validation is in the github action
       | ci for the real-world app example from above:
       | https://github.com/neosmart/EasyBCD-Localization/blob/master...
        
       | sandreas wrote:
       | I wonder why translations always tend to need an extra format or
       | framework. Is replacing or customizing translation files after
       | deployment a thing or could it be just (generated) compile time
       | CODE in the used language based on a simple common format (like
       | gettext with macros)?
       | 
       | I did A LOT of translation work and in the end the best, easiest
       | and fastest solution always was a list / map of key value pairs,
       | where key was a unique translation identifier and the value was
       | the translation in one language optionally having placeholders
       | (e.g. for numbers) combined with a simple macro (best case) /
       | function / method similar to sprintf.
       | 
       | Even plural forms should get their own unique identifier -
       | automatic pluralization always failed for my use cases (russian
       | has several different plural forms depending on the context).
        
         | svantex wrote:
         | Quite so, and in the case of resx, that's exactly what you're
         | getting. A list of key-value-pairs, no support for
         | pluralisation either (as you say, it gets hard. I understand
         | Polish for example is very complex, perhaps Russian is the
         | same?).
         | 
         | I never had a problem with resx and .net satellite assemblies
         | and all that as far as the format goes. But it's always been an
         | issue how to involved translators in a way that's both simple
         | for the translators, safe for the quality and as automated as
         | possible in bringing the translations back to the app.
        
           | sandreas wrote:
           | That may be the case, but resx is a quite bloated XML format
           | for a simple key value pair listing. Besides that, resx is
           | yet another format for the same thing.
           | 
           | I think the solution is quite simple:                 - One
           | unified key value pair format (for translators and GUI tools)
           | - One intermediate format that is programming language
           | specific (it could be generated code or highly integrated
           | formats like resx)       - A simple tool that can
           | transliterate between those two formats
           | 
           | Workflow example:                 - Export a unified format
           | file from resx with placeholders for the translations       -
           | Translators: Here you go, use your GUI tools on this       -
           | Get back the translated unified format       - Import a
           | unified format file to resx
        
       | sasakrsmanovic2 wrote:
       | Unclear to me where this app runs as far as deskop goes?
       | Persumably windows, so is there not a way to reuse winui
       | localization via WinAppsSDK https://learn.microsoft.com/en-
       | us/windows/apps/winui/winui3/...
        
         | svantex wrote:
         | If you're referring to my app, Xecrets Ez (
         | https://www.axantum.com/ ), it runs in Windows, macOS and
         | Linux.
         | 
         | As mentioned, the issue I'm trying to solve is not the code
         | end. Resx works fine once it's there. It's the translator end.
         | How to present the texts and translations and context etc to
         | the human, often non-technical, translators and often many and
         | one translator might only translate a few strings, then another
         | one etc. So it has to be real easy to use and gain access to.
         | Can't for example ask them to install a piece of software.
         | Finally, once a text has been translated, how to get it back to
         | the app as easy and preferably as automated as possible.
        
       ___________________________________________________________________
       (page generated 2024-05-16 23:01 UTC)