{"id":24775534,"url":"https://github.com/sandrock/markalize","last_synced_at":"2026-04-29T11:02:12.482Z","repository":{"id":69086136,"uuid":"147398270","full_name":"sandrock/Markalize","owner":"sandrock","description":"Better .NET localization ","archived":false,"fork":false,"pushed_at":"2019-03-14T07:43:29.000Z","size":37,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-01-28T19:45:33.004Z","etag":null,"topics":["cultureinfo","dotnet","languages","localization","resx"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sandrock.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-09-04T19:06:53.000Z","updated_at":"2019-03-14T08:15:51.000Z","dependencies_parsed_at":"2023-06-09T00:45:09.595Z","dependency_job_id":null,"html_url":"https://github.com/sandrock/Markalize","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sandrock/Markalize","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrock%2FMarkalize","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrock%2FMarkalize/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrock%2FMarkalize/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrock%2FMarkalize/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sandrock","download_url":"https://codeload.github.com/sandrock/Markalize/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrock%2FMarkalize/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32422532,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T06:29:02.080Z","status":"ssl_error","status_checked_at":"2026-04-29T06:29:00.631Z","response_time":110,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cultureinfo","dotnet","languages","localization","resx"],"created_at":"2025-01-29T06:54:41.029Z","updated_at":"2026-04-29T11:02:12.475Z","avatar_url":"https://github.com/sandrock.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\nMarkalize\n===================\n\nA better way to localize your apps in .NET.\n\nWARNING - Project not ready for production\n-------------------------------------------\n\nI am throwing code all over the place. This is NO READY FOR PRODUCTION. Not even for you to test.\n\nCome back later to follow the project status.\n\n\nWhat's wrong with resource files? (resx)\n-------------------------------------------\n\n* Must* be implemented at compile-time with many dirty practices.\n  * May be [loaded at run time](https://docs.microsoft.com/en-us/dotnet/framework/resources/working-with-resx-files-programmatically)\n  * The sattelite assembly thing has [obscure code](https://github.com/Microsoft/msbuild/blob/e70a3159d64f9ed6ec3b60253ef863fa883a99b1/src/Tasks/CultureInfoCache.cs#L51) and [obscure issues](https://github.com/Microsoft/msbuild/issues/1454)\n    * You [cannot prevent the compile time from making sattelite assemblies](https://github.com/Microsoft/msbuild/issues/3064#issuecomment-418540984)\n    * And some file names [are not allowed to be used](https://github.com/aspnet/Tooling/issues/1066)\n* The editor is too old\n  * Keyboard shortcut often end in undesired edits\n  * Some moves ends with lost keys\n  * Dark theme is not supported\n* Pluralization and genre are not supported\n* A strange comment is autoblackmagicaly added to resx file every time you change them. \n  * This comment takes up assembly space if you have many files and assemblies in a project.\n  * The comment keeps on coming back. Always.\n\n\nIsn't there anything else already available?\n----------------------------------------------\n\n* pot files are not well tooled for .NET\n* TODO: document more \n\n\nWhat can we change?\n---------------------\n\nDon't you think a simple text file can do the job? I do.\n\nDon't you think anyone should be able to provide localization in a file that is not hard to edit? I do.\n\nDon't you like markdown? There is support in here.\n\nAren't you tired of coding tricks to handle singular/plural/masculine/feminine variants? I am.\n\n\nLet's dive\n-------------------\n\nSay that we are going to start with assembly-embedded files just like resx files. Don't worry, more options are about to come.\n\n### Keys and values\n\nLocalization files are key-value pairs. It does not change here. Here is a markdown-compatible default resource file.\n\n```\nHello =      Welcome on our website.\nNavigation = Here you will find various links to find our stuff.\nIntro        This website provides contents and ideas for developers and foxes. Note \\\n             that the equal sign is not required. Indentation is only decorative. You \\\n             can do without.\n```\n\n\n### Prefixes\n\nNow say that you have many pages or many sections. You wish to prefix your keys to denote that.\n\n```\nPage1_Title First page!\nPage2_Desc  It's quite boring to play with prefixes! Can we do better?\n\nPage2_\n=================\n\nTitle   Second page!\nDesc    Oh yeah! The title acts as a prefix. Can't it be a bit more sexy?\n\nThird page [Page3_]\n=======================\n\nTitle  Now we've got somethin'!\nDesc   What if my page has sections?\n\nSection 1 [S1_]\n-----------------\n\nTitle  Page 1 \u003e Section 1 \u003e Title\nDesc   2 levels of title are supported. The key to access the current value is `Page3_S1_Desc`.\nDesc2  And if I want to \"leave the title\"?\n\n---\n\nHorizontal bars reset the prefix stack. Note that the current line will create a key `Horizontal` that I don't need. The beauty of doing markdown is that you are quite free to mix localized values, text and formating.\n\n// This is a commented line. This may help if the first word of your line interferes with another line.\n```\n\n### Lines and wraping\n\nAs you can see, each line defines a key and a value. How can we do multi-line values? Now double quotes are coming.\n\n```\nKey124 The backslash does not produce \\\n       a new line. It only makes the file look better.\nKey125 \"This value includes \na line feed.\"\nKey126 \"\"This line includes \"quotes\" that don't need to be escaped. \"\"\nKey127 \"\"\"\"\"\"You can use as many delimiting quotes as you wish\"\"\"\"\"\"\nKey128 \"\"The backslash is available to avoid lines\\\nfrom becoming too long. \"\"\n```\n\n* When using the backslash:\n  * no line feed is produced\n  * all lines are trimmed from white spaces (don't forget to end your line with a space)\n* When using quotes:\n  * a line feed is a line feed\n  * lines are not trimmed from white spaces\n  * using a backslash will only enhance the look\n\n\n### File names (and dimensions)\n\nYou will need multiple files to handle mumtiple cultures. You will be able to handle more than culture.\n\nIf you don't know how \"cultures\" (languages, locales) and localization work in .NET, see [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) and [CultureInfo class](https://docs.microsoft.com/en-gb/dotnet/api/system.globalization.cultureinfo?view=netframework-4.7.2).\n\nSome languages allow two forms of the \"you\" pronoun, like in french. This is the [T-V distinction](https://en.wikipedia.org/wiki/T%E2%80%93V_distinction). \"Tu\" (thou) is familiar and is called \"tutoiement\". \"Vous\" is not and is called \"vouvoiement\". Say you want your users to choose between these forms...\n\nThe principle here is to think about dimensions. Culture names already have at least 2 dimensions: the language and the region. You can add more dimensions! Do you like a subculture? You can create localization variants for your users.\n\n* Language dimension (standard): \n  * fr: french\n  * en: english\n  * de: german\n* Region dimension (standard): \n  * FR: France\n  * CA: Canada\n  * US: USA\n  * GB: Great Britain\n  * DE: Germany\n* T-V dimension (known linguistic subtelty): \n  * Vos: vouvoiement (default for french)\n  * Tu: tutoiement (default for english)\n* Subculture dimension (developer created): \n  * Kaamelott: a popular french TV series\n  * Star Trek: a popular sci-fi TV series\n\nWith dimensions, you can compose localizations for everyone.\n\n* File `Website.L-en.R-US.Default.md`\n  * Dimension `L` (language) is `en` (engligh)\n  * Dimension `R` (region)   is `US` (USA)\n  * The `Default` part signals that this is the ultimate fallback resource file. This is optional.\n* File `Website.L-fr.R-FR.md`\n  * Dimension `L` (language) is `fr` (french)\n  * Dimension `R` (region)   is `FR` (France)\n* File `Website.L-fr.R-FR.T-Tu.md`\n  * Dimension `L` (language) is `fr` (french)\n  * Dimension `R` (region)   is `FR` (France)\n  * Dimension `T` (T-V dimension) is `Tu` (Tutoiement)\n* File `Website.L-en.R-US.S-StarTrek.md`\n  * Dimension `L` (language) is `en` (engligh)\n  * Dimension `R` (region)   is `US` (USA)\n  * Dimension `S` (TV series dimension) is `StarTrek` (refering to Star Trek)\n\nNow your app: \n\n* defaults to english\n* supports 2 forms of french\n* supports a special star-trek-english with known references to this subculture\n\nIt's important to keep the 2 standard dimensions (language and region) in order to keep [formating right](https://docs.microsoft.com/en-us/globalization/locale/locale-and-culture).\n\nSo. Create those files in visual studio. Set them as \"embedded resource\".\n\n```\nvar set = new ResourceSet();\nset.LoadFromAssembly(typeof(ResourceSetTests).Assembly, \"Resources/Website\");\n```\n\nThe `set` variable will make sense of all your resource files that look like `Website.***.md`. Now, let's localize.\n\n```\nvar localizer1 = set.GetLocalizer();\nlocalizer1.Localize(\"Hello\")    .ShouldEqual(\"Welcome on our website.\") // defaults to english\nlocalizer1.Localize(\"GoodBye\")  .ShouldEqual(\"Good bye.\")               // defaults to english\n\nvar usersCulture = new CultureInfo(\"fr-FR\");\nvar localizer2 = set.GetLocalizer(usersCulture, \"T-Tu\");\nlocalizer1.Localize(\"Hello\")    .ShouldEqual(\"Nous te souhaitons la bienvenue.\") // french tutoiement\nlocalizer1.Localize(\"GoodBye\")  .ShouldEqual(\"A bient�t !\")                      // french tutoiement\n\nvar localizer3 = set.GetLocalizer(\"L-en\", \"S-StarTrek\");\nlocalizer3.Localize(\"Hello\")    .ShouldEqual(\"Welcome on our website.\") // defaults to english\nlocalizer3.Localize(\"GoodBye\")  .ShouldEqual(\"Live long and prosper.\")  // Star Trek specific\n```\n\nFor performance reasons, you should:\n\n* Provide a finite list of supported culture to the user\n  * letting the user compose its culture may lead to unexpected memory usage\n  * use the inline syntax to identify cultures (L-fr.R-fr.T-Tu, L-en.R-US)\n* Keep localizer objects in a static readonly field to avoid allocating too much memory\n\n\n### Go runtime\n\nWhat you just did with assembly-embedded files can also be done with filesystem-file or files from a database. This will allow you to create GUI for your users to localize texts. \n\n\n### Going further\n\nSee the [format specifications](FORMAT-SPEC.md).\n\n\nRoadmap\n--------------\n\n### Documentation tasks\n\n* [ ] Singulars and plurals\n\n\n### Release 1\n\n* [ ] Syntax fully working\n  * [x] Simple value\n  * [x] Simple value + backslash\n  * [x] Quoted value\n  * [ ] Quoted value + backslash\n  * [ ] Quoted verbatim value\n  * [ ] Quoted verbatim value + backslash\n  * [ ] Title 1\n  * [ ] Title 2\n  * [x] Comments (fenced code blocks)\n* [ ] Loading assembly   files fully working\n  * [ ] Determine exact syntax\n  * [ ] Loader code\n  * [ ] Specialized exception type\n* [ ] Loading filesystem files fully working\n  * [ ] Loader code\n* [ ] Loading custom     files fully working\n  * [ ] Loader code\n* [ ] Localizer fully working\n* [ ] Inline culture ID syntax helpers\n* [ ] Compatibility with CultureInfo and classic dev tasks\n* [ ] Sample console app\n* [ ] Great README\n* [ ] Basic format specifications\n\n### Release 2\n\n* [ ] Sample domain library\n* [ ] Sample MVC app\n* [ ] Sample WPF app\n* [ ] Ease load from filesystem\n* [ ] Basic resx to markalize converter\n* [ ] Ease caching of localizer objects\n* [ ] In-document dimensions and options\n\n### Future work\n\n* [ ] Serialization capabilities (document object model)\n  * Read file as object\n  * Change object (from a GUI)\n  * Write object to file (comments and titles are preserved)\n* [ ] WPF GUI with translation capabilities\n* [ ] Console app to auto translate a file\n* [ ] Intelli-sense addon \n* [ ] Developer options to detect missing translations\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsandrock%2Fmarkalize","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsandrock%2Fmarkalize","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsandrock%2Fmarkalize/lists"}