wip: typed literal syntax for locale messages

This commit is contained in:
Benjamin Singleton 2026-02-23 12:28:36 -06:00
parent d258014471
commit e48b6c8708
6 changed files with 297 additions and 125 deletions

View file

@ -1,77 +1,77 @@
localeName = English
vilanticLangs-viossa = Viossa
vilanticLangs-wodox = Wodoch
navbar-whatIsViossa = What is Viossa?
navbar-resources = Resources
navbar-kotoba = Kotoba
home-sections-whatIsViossa-title = What is Viossa?
home-sections-whatIsViossa-body = Viossa is a community-created artificial pidgin language, created to simulate the formation of natural pidgin languages. Viossa is characterized by its lack of standardization, with each speaker developing a personal idiolect. Spelling and pronunciation can vary greatly, and serve as a form of personal self-expression. Viossa is learnt and taught entirely by immersion — translation is prohibited while learning.
home-sections-historyOfViossa-title = History of Viossa
home-sections-historyOfViossa-body = Viossa began as a Skype group in 2014, created by members of the r/conlangs community on Reddit, as an experiment to simulate the formation of a pidgin language. Pidgins are simplified languages resulting from contact between populations with no shared common language. Unlike most pidgins, which usually have two to three contributor languages, Viossa comes from many diverse languages. This is because people from all around the world helped to contribute to Viossa's vocabulary.
home-sections-community-title = Community
home-sections-community-body = The Viossa community is rich and colourful, drawing from many global traditions due to its worldwide online membership. Since the teaching culture puts an emphasis on linguistic immersion, and discourages prescriptivism, the culture of Viossa is as diverse and varied as the language and the people who speak it. For many, their personal dialect is a key form of identity and expression. The fluid nature of Viossa and lack of defined meanings makes Viossa popular for creative purposes, such as poetry and songwriting.
home-images-viossaFlag-alt = Flag of the Viossa Language
resources-title = Learning Resources
resources-resources-discord-title = Discord Server
resources-resources-discord-subtitle = This is where most of the action happens! Hop on in!
resources-resources-discord-desc = Originally started in 2015 something something read the rules here, then click the link below to join!
resources-resources-discord-buttons-join-label = Join
resources-resources-discord-buttons-rules-label = Rules
resources-images-discordLogo-alt = Discord logo
kotoba-title = Tropos-agnostic search
kotoba-searchHelp = To searcn tropos-agnostically, enter a term below.
discord-rulesPage-title = Discord Server Rules
discord-rulesPage-overview-title = Overview
discord-rulesPage-overview-help = Click any rule to see details.
discord-rulesPage-rules-noTranslation-overview-text = | No translation! Do not translate to/from Viossa on the server, except the big four translatables (you can learn in hard mode without them!)
discord-rulesPage-rules-noTranslation-overview-subtext = |~
discord-rulesPage-rules-noTranslation-section-header = Rule { $ruleNumber }: No translation
discord-rulesPage-rules-noTranslation-section-body =
| Translation is not how we learn and teach Viossa. Instead, we teach using pictures, diagrams, video calls, and other aids to couple words to meaning.
| On the Viossa Diskordserver, you are allowed to translate the following four words. If you want an extra challenge, don't unspoiler the text:
| *TODO - big 4*
| Outside of the teaching-learning cycle, we also make an exception for artistic translations (such as those of songs, books, or poems), as well as for academic translations (such as for a formal research paper). In both cases, this exception is dependent on translations of either class appearing in the appropriate place. If you're not sure where that is, please ask.
| Additionally, please don't attempt to derive or share translation-based learning materials on-server, or poach members for such a purpose.
discord-rulesPage-rules-lfsv-overview-text = | If it's understood, it's Viossa.
discord-rulesPage-rules-lfsv-overview-subtext = |~
discord-rulesPage-rules-lfsv-section-header = Rule { $ruleNumber }: If it's understood, it's Viossa
discord-rulesPage-rules-lfsv-section-body =
| All that is required to speak Viossa is that other speakers be able to understand you. There is no right or wrong way to speak or write, and no global standard.
| However, Viossa is a collaborative group project: members should strive to make others understand them, and in return make an effort to understand others.
discord-rulesPage-rules-viossaOnlyChats-overview-text = | The chats in the Viossa Only category are Viossa only.
discord-rulesPage-rules-viossaOnlyChats-overview-subtext = |~
discord-rulesPage-rules-viossaOnlyChats-section-header = Rule { $ruleNumber }: Viossa-only chats
discord-rulesPage-rules-viossaOnlyChats-section-body =
| Chats in the Viossa Only section do not permit English. If you must use English to coach learners on the learning process, go to **#meta** instead.
| This doesn't mean that other channels are English-only, though! Viossa is allowed everywhere.
discord-rulesPage-rules-sfw-overview-text = | This server is SFW. No sexually explicit, gory, or violent content.
discord-rulesPage-rules-sfw-overview-subtext = |~
discord-rulesPage-rules-sfw-section-header = Rule { $ruleNumber }: SFW
discord-rulesPage-rules-sfw-section-body =
| If a mod does not like what you have posted, they will inform you; see [Rule 6](internal.replace:#rule-6). This is a public Discord server; think before you post.
discord-rulesPage-rules-respectOthers-overview-text = | Don't use hate speech, and respect each other.
discord-rulesPage-rules-respectOthers-overview-subtext = |~
discord-rulesPage-rules-respectOthers-section-header = Rule { $ruleNumber }: Respect one another
discord-rulesPage-rules-respectOthers-section-body =
| Respect one another. Using slurs or hate speech against others, whether on- or off-server, or advocating for violence are not welcome. This is an LGBTQ+ friendly international community.
discord-rulesPage-rules-respectStaff-overview-text = | Respect the rulings of the staff (**@Yewald** and **@Yewaldnen**).
discord-rulesPage-rules-respectStaff-overview-subtext = |~
discord-rulesPage-rules-respectStaff-section-header = Rule { $ruleNumber }: Respect the staff's rulings
discord-rulesPage-rules-respectStaff-section-body =
| The word of staff (the Yewald as well as the Yewaldnen) is final, and they may kick, ban, or mute members or change members' access permissions to make sure this environment stays respectful and puts the Viossa community first.
| Appeals will always be considered, and if you feel that a mod action was inappropriate, you can DM any Yewald or open a ticket with YAGPDB's /tickets open command.
| If you are banned, there will be instructions on how to appeal the ban, however, please take the time to reflect on the ban reason before appealing.
discord-rulesPage-rules-controversialTopics-overview-text = | Discussion of controversial topics (politics, war, etc.) should be directed to **#polite**, which requires the **@Ike** role to view, which is itself locked behind **@Viossadjin** and **@mellandjin**.
discord-rulesPage-rules-controversialTopics-overview-subtext = | **#feels-and-advice** is for talking about your feelings openly, but we draw the line at suicidal or violent ideation. These are trains of thought to be brought to a therapist, and are not jokes. Because of their seriousness, they simply don't belong here.
discord-rulesPage-rules-controversialTopics-section-header = Rule { $ruleNumber }: #polite and ike
discord-rulesPage-rules-controversialTopics-section-body =
| Many are life's troubling realities, and vast is our need to discuss them. The ike category is an opt-in set of chats where discussion of heavy, sensitive, or potentially contentious topics is allowed, provided that users are especially respectful of each other during such discussions. By accepting the ike role, you agree to adhere to this rule and encourage others to do the same.
| # Venting vs seeking advice
| Sometimes you want to let people know that you're dealing with an issue and just be acknowledged, other times you want help in solving a problem. If you are open to one but not the other, it's often a good idea to let people know as part of the discussion so that you can receive the kind of responses you are looking for.
| # Self-harm and Violence
| While discussing self-harm in general is allowed (with appropriate and clear use of content warnings), this server in itself is not an emergency mental health resource, and is not a substitute for professional help. Asking others for advice in finding support or resources off-server is fine, but asking others to participate in talking you down is inappropriate.
| You should not use this space to:
| - express intent or desire to harm yourself or others
| - solicit help in stopping yourself from harming yourself or someone else
| By crossing these boundaries, please be aware that you are asking members of the server (including moderators and the owner) to perform a role for which they are not trained or equipped. At the moderators' sole discretion, this may not be tolerated and may result in a warning, timeout, removal of the **@ike** role, or removal from the server.
| If you are struggling with thoughts of this nature, but are not immediately in danger, please consider seeking counseling. If you are experiencing an immediate crisis, please call **988** (in the United States), **999** (in the UK), or locate an emergency hotline appropriate for you. A list of resources by country exists here: [](external.new:https://blog.opencounseling.com/suicide-hotlines/)
localeName = "English"
vilanticLangs-viossa = "Viossa"
vilanticLangs-wodox = "Wodoch"
navbar-whatIsViossa = "What is Viossa?"
navbar-resources = "Resources"
navbar-kotoba = "Kotoba"
home-sections-whatIsViossa-title = "What is Viossa?"
home-sections-whatIsViossa-body = "Viossa is a community-created artificial pidgin language, created to simulate the formation of natural pidgin languages. Viossa is characterized by its lack of standardization, with each speaker developing a personal idiolect. Spelling and pronunciation can vary greatly, and serve as a form of personal self-expression. Viossa is learnt and taught entirely by immersion — translation is prohibited while learning."
home-sections-historyOfViossa-title = "History of Viossa"
home-sections-historyOfViossa-body = "Viossa began as a Skype group in 2014, created by members of the r/conlangs community on Reddit, as an experiment to simulate the formation of a pidgin language. Pidgins are simplified languages resulting from contact between populations with no shared common language. Unlike most pidgins, which usually have two to three contributor languages, Viossa comes from many diverse languages. This is because people from all around the world helped to contribute to Viossa's vocabulary."
home-sections-community-title = "Community"
home-sections-community-body = "The Viossa community is rich and colourful, drawing from many global traditions due to its worldwide online membership. Since the teaching culture puts an emphasis on linguistic immersion, and discourages prescriptivism, the culture of Viossa is as diverse and varied as the language and the people who speak it. For many, their personal dialect is a key form of identity and expression. The fluid nature of Viossa and lack of defined meanings makes Viossa popular for creative purposes, such as poetry and songwriting."
home-images-viossaFlag-alt = "Flag of the Viossa Language"
resources-title = "Learning Resources"
resources-resources-discord-title = "Discord Server"
resources-resources-discord-subtitle = "This is where most of the action happens! Hop on in!"
resources-resources-discord-desc = "Originally started in 2015 something something read the rules here, then click the link below to join!"
resources-resources-discord-buttons-join-label = "Join"
resources-resources-discord-buttons-rules-label = "Rules"
resources-images-discordLogo-alt = "Discord logo"
kotoba-title = "Tropos-agnostic search"
kotoba-searchHelp = "To searcn tropos-agnostically, enter a term below."
discord-rulesPage-title = "Discord Server Rules"
discord-rulesPage-overview-title = "Overview"
discord-rulesPage-overview-help = "Click any rule to see details."
discord-rulesPage-rules-noTranslation-overview-text = md "No translation! Do not translate to/from Viossa on the server, except the big four translatables (you can learn in hard mode without them!)"
discord-rulesPage-rules-noTranslation-overview-subtext = md --
discord-rulesPage-rules-noTranslation-section-header = "Rule { $ruleNumber }: No translation"
discord-rulesPage-rules-noTranslation-section-body = md
"Translation is not how we learn and teach Viossa. Instead, we teach using pictures, diagrams, video calls, and other aids to couple words to meaning."
"On the Viossa Diskordserver, you are allowed to translate the following four words. If you want an extra challenge, don't unspoiler the text:"
"*TODO - big 4*"
"Outside of the teaching-learning cycle, we also make an exception for artistic translations (such as those of songs, books, or poems), as well as for academic translations (such as for a formal research paper). In both cases, this exception is dependent on translations of either class appearing in the appropriate place. If you're not sure where that is, please ask."
"Additionally, please don't attempt to derive or share translation-based learning materials on-server, or poach members for such a purpose."
discord-rulesPage-rules-lfsv-overview-text = md "If it's understood, it's Viossa."
discord-rulesPage-rules-lfsv-overview-subtext = md --
discord-rulesPage-rules-lfsv-section-header = "Rule { $ruleNumber }: If it's understood, it's Viossa"
discord-rulesPage-rules-lfsv-section-body = md
"All that is required to speak Viossa is that other speakers be able to understand you. There is no right or wrong way to speak or write, and no global standard."
"However, Viossa is a collaborative group project: members should strive to make others understand them, and in return make an effort to understand others."
discord-rulesPage-rules-viossaOnlyChats-overview-text = md "The chats in the Viossa Only category are Viossa only."
discord-rulesPage-rules-viossaOnlyChats-overview-subtext = md --
discord-rulesPage-rules-viossaOnlyChats-section-header = "Rule { $ruleNumber }: Viossa-only chats"
discord-rulesPage-rules-viossaOnlyChats-section-body = md
"Chats in the Viossa Only section do not permit English. If you must use English to coach learners on the learning process, go to **#meta** instead."
"This doesn't mean that other channels are English-only, though! Viossa is allowed everywhere."
discord-rulesPage-rules-sfw-overview-text = md "This server is SFW. No sexually explicit, gory, or violent content."
discord-rulesPage-rules-sfw-overview-subtext = md --
discord-rulesPage-rules-sfw-section-header = "Rule { $ruleNumber }: SFW"
discord-rulesPage-rules-sfw-section-body = md
"If a mod does not like what you have posted, they will inform you; see [Rule 6](internal.replace:#rule-6). This is a public Discord server; think before you post."
discord-rulesPage-rules-respectOthers-overview-text = md "Don't use hate speech, and respect each other."
discord-rulesPage-rules-respectOthers-overview-subtext = md --
discord-rulesPage-rules-respectOthers-section-header = "Rule { $ruleNumber }: Respect one another"
discord-rulesPage-rules-respectOthers-section-body = md
"Respect one another. Using slurs or hate speech against others, whether on- or off-server, or advocating for violence are not welcome. This is an LGBTQ+ friendly international community."
discord-rulesPage-rules-respectStaff-overview-text = md "Respect the rulings of the staff (**@Yewald** and **@Yewaldnen**)."
discord-rulesPage-rules-respectStaff-overview-subtext = md --
discord-rulesPage-rules-respectStaff-section-header = "Rule { $ruleNumber }: Respect the staff's rulings"
discord-rulesPage-rules-respectStaff-section-body = md
"The word of staff (the Yewald as well as the Yewaldnen) is final, and they may kick, ban, or mute members or change members' access permissions to make sure this environment stays respectful and puts the Viossa community first."
"Appeals will always be considered, and if you feel that a mod action was inappropriate, you can DM any Yewald or open a ticket with YAGPDB's /tickets open command."
"If you are banned, there will be instructions on how to appeal the ban, however, please take the time to reflect on the ban reason before appealing."
discord-rulesPage-rules-controversialTopics-overview-text = md "Discussion of controversial topics (politics, war, etc.) should be directed to **#polite**, which requires the **@Ike** role to view, which is itself locked behind **@Viossadjin** and **@mellandjin**."
discord-rulesPage-rules-controversialTopics-overview-subtext = md "**#feels-and-advice** is for talking about your feelings openly, but we draw the line at suicidal or violent ideation. These are trains of thought to be brought to a therapist, and are not jokes. Because of their seriousness, they simply don't belong here."
discord-rulesPage-rules-controversialTopics-section-header = "Rule { $ruleNumber }: #polite and ike"
discord-rulesPage-rules-controversialTopics-section-body = md
"Many are life's troubling realities, and vast is our need to discuss them. The ike category is an opt-in set of chats where discussion of heavy, sensitive, or potentially contentious topics is allowed, provided that users are especially respectful of each other during such discussions. By accepting the ike role, you agree to adhere to this rule and encourage others to do the same."
"# Venting vs seeking advice"
"Sometimes you want to let people know that you're dealing with an issue and just be acknowledged, other times you want help in solving a problem. If you are open to one but not the other, it's often a good idea to let people know as part of the discussion so that you can receive the kind of responses you are looking for."
"# Self-harm and Violence"
"While discussing self-harm in general is allowed (with appropriate and clear use of content warnings), this server in itself is not an emergency mental health resource, and is not a substitute for professional help. Asking others for advice in finding support or resources off-server is fine, but asking others to participate in talking you down is inappropriate."
"You should not use this space to:"
"- express intent or desire to harm yourself or others"
"- solicit help in stopping yourself from harming yourself or someone else"
"By crossing these boundaries, please be aware that you are asking members of the server (including moderators and the owner) to perform a role for which they are not trained or equipped. At the moderators' sole discretion, this may not be tolerated and may result in a warning, timeout, removal of the **@ike** role, or removal from the server."
"If you are struggling with thoughts of this nature, but are not immediately in danger, please consider seeking counseling. If you are experiencing an immediate crisis, please call **988** (in the United States), **999** (in the UK), or locate an emergency hotline appropriate for you. A list of resources by country exists here: [](external.new:https://blog.opencounseling.com/suicide-hotlines/)"

View file

@ -1,3 +1,3 @@
localeName = Viossa
home-sections-whatIsViossa-title = Kafaen afto Viossa
home-sections-whatIsViossa-body = Viossa tte glossa mahena grun vi nai vil fshtojena na bakadjin, grun vi svinnur ja! De aldjin zovti lera ne
localeName = "Viossa"
home-sections-whatIsViossa-title = "Kafaen afto Viossa"
home-sections-whatIsViossa-body = "Viossa tte glossa mahena grun vi nai vil fshtojena na bakadjin, grun vi svinnur ja! De aldjin zovti lera ne"

View file

@ -1,22 +1,22 @@
localeName = wodox
vilanticLangs-viossa = viosox
vilanticLangs-wodox = wodox
navbar-whatIsViossa = viosox e ano?
navbar-resources = tropos
navbar-kotoba = mot o viosox
home-sections-whatIsViossa-title = viosox e ano?
home-sections-whatIsViossa-body = viosox e hez ox pamzal, zoz stende zalkun tuo mit multa nengwi ox. zal o viosox stende lik zal o hez il ox keta, zalilkun wi tuo mit multa nengwi ox. mono i fal o viosox stendenai; omni axsi o viosox zal nengokun fal o viosox, de falmot wi falax o il stende e keko trenengwi tua o nengwi stende, ge fala e keko lik ro o tuo viosoxsi. genil viosox ibe il wi nengwi stende axkun ge pisakun po tuo ox — stende gen muskunnai mit zaiox.
home-sections-historyOfViossa-title = zal o viosox
home-sections-historyOfViossa-body = wi o zal o viosox stende po multa o Skype wi 2014 ibe stendera o multa r/conlangs o Reddit. zalsi o viosox danzalgo hez, tuo zal o viosox e lik zal o hez il ox keta, zalil tuo ox mit nengwi multa ox ibe zalsi fiemnaikun sama i ox. viosox e nengwi tuo ox pamzal keta; zalilkun keko ox keta mit lik du wi tre ox, aga zalil viosox mit multa wi plus obo o ox na il ox keta ibe zalsi o viosox stende po multa mi o mo.
home-sections-community-title = viosoxsi
home-sections-community-body = nengwi multa ro o viosoxsi stende ibe stendenura po nengwi multa mi o mo ge wekakunnura zai nengwi viosoxsi po jilobo. ibe mono i fal o viosox stendenai ge ibe viosoxsi zalkun nengwi multa fal o viosox, de nengwi zoz ko o ro lik ro o viosoxsi stende po ro o viosa. po multa hez viosoxsi, falmot wi falax o tuo stende stende po ro o tuo stende. ibe mono i fal o viosox stendenai ge ibe ro o mot inkun nengwi po nengwi viosoxsi, de multa stende amanata hez, zal zalgonukun surat au mola au sucik.
home-images-viossaFlag-alt = fomma o viosox
resources-title = tropos o gen
resources-resources-discord-title = server o Diskord
resources-resources-discord-subtitle = axilkun ge genilkun po ce! wekatutsa!
resources-resources-discord-desc = danzalil hez server po 2015. ibe dutukun musra po ce, de ibe wiftutsakun dof po pam, de wekatukun po server!
resources-resources-discord-buttons-join-label = wekatutsa
resources-resources-discord-buttons-rules-label = musra
resources-images-discordLogo-alt = surat o Diskord
kotoba-title = zalkuketutsa mot o viosox mit il o omni falmot
kotoba-searchHelp = ibe tastatukun il falmot o mot o viosox po pam, de zalkuketukun.
localeName = "wodox"
vilanticLangs-viossa = "viosox"
vilanticLangs-wodox = "wodox"
navbar-whatIsViossa = "viosox e ano?"
navbar-resources = "tropos"
navbar-kotoba = "mot o viosox"
home-sections-whatIsViossa-title = "viosox e ano?"
home-sections-whatIsViossa-body = "viosox e hez ox pamzal, zoz stende zalkun tuo mit multa nengwi ox. zal o viosox stende lik zal o hez il ox keta, zalilkun wi tuo mit multa nengwi ox. mono i fal o viosox stendenai; omni axsi o viosox zal nengokun fal o viosox, de falmot wi falax o il stende e keko trenengwi tua o nengwi stende, ge fala e keko lik ro o tuo viosoxsi. genil viosox ibe il wi nengwi stende axkun ge pisakun po tuo ox — stende gen muskunnai mit zaiox."
home-sections-historyOfViossa-title = "zal o viosox"
home-sections-historyOfViossa-body = "wi o zal o viosox stende po multa o Skype wi 2014 ibe stendera o multa r/conlangs o Reddit. zalsi o viosox danzalgo hez, tuo zal o viosox e lik zal o hez il ox keta, zalil tuo ox mit nengwi multa ox ibe zalsi fiemnaikun sama i ox. viosox e nengwi tuo ox pamzal keta; zalilkun keko ox keta mit lik du wi tre ox, aga zalil viosox mit multa wi plus obo o ox na il ox keta ibe zalsi o viosox stende po multa mi o mo."
home-sections-community-title = "viosoxsi"
home-sections-community-body = "nengwi multa ro o viosoxsi stende ibe stendenura po nengwi multa mi o mo ge wekakunnura zai nengwi viosoxsi po jilobo. ibe mono i fal o viosox stendenai ge ibe viosoxsi zalkun nengwi multa fal o viosox, de nengwi zoz ko o ro lik ro o viosoxsi stende po ro o viosa. po multa hez viosoxsi, falmot wi falax o tuo stende stende po ro o tuo stende. ibe mono i fal o viosox stendenai ge ibe ro o mot inkun nengwi po nengwi viosoxsi, de multa stende amanata hez, zal zalgonukun surat au mola au sucik."
home-images-viossaFlag-alt = "fomma o viosox"
resources-title = "tropos o gen"
resources-resources-discord-title = "server o Diskord"
resources-resources-discord-subtitle = "axilkun ge genilkun po ce! wekatutsa!"
resources-resources-discord-desc = "danzalil hez server po 2015. ibe dutukun musra po ce, de ibe wiftutsakun dof po pam, de wekatukun po server!"
resources-resources-discord-buttons-join-label = "wekatutsa"
resources-resources-discord-buttons-rules-label = "musra"
resources-images-discordLogo-alt = "surat o Diskord"
kotoba-title = "zalkuketutsa mot o viosox mit il o omni falmot"
kotoba-searchHelp = "ibe tastatukun il falmot o mot o viosox po pam, de zalkuketukun."

View file

@ -6,6 +6,7 @@ import {
} from "./setup";
import type { FluentVariable } from "@fluent/bundle";
import { parseMarkdown, type Markdown } from "./markdown";
import { ignore } from "@/utils/ignore";
export const configMessageSymbol: unique symbol = Symbol("configMessage");
export interface ConfigMessage<
@ -162,17 +163,6 @@ export function compileLocale<Config extends LocaleConfig>(
};
}
const allVariantsRes = computeAllVariants(pattern);
if (allVariantsRes.type === "err") {
return {
type: "err",
err: `Failed to compute variants for key \`${id}\`:\n${allVariantsRes.err}`,
};
}
const allVariants = allVariantsRes.ok;
console.log(allVariants);
// validate placeables
console.log(pattern);
if (typeof pattern !== "string") {
@ -229,6 +219,17 @@ export function compileLocale<Config extends LocaleConfig>(
}
}
const allVariantsRes = computeAllVariants(pattern);
if (allVariantsRes.type === "err") {
return {
type: "err",
err: `Failed to compute variants for key \`${id}\`:\n${allVariantsRes.err}`,
};
}
const allVariants = allVariantsRes.ok;
console.log(allVariants);
// create function
const markdown = configValue.markdown;
const compiledFnRes = ((): Result<
@ -237,12 +238,27 @@ export function compileLocale<Config extends LocaleConfig>(
string
> => {
if (markdown !== null) {
// typecheck markdown
const markdownSlots = markdown.slots ?? [];
// check if all variants are valid markdown
for (const variant of allVariants) {
const markdownRes = parseMarkdown(
const markdownLiteralRes = parseMessageLiteral(
"md",
variant.string,
);
if (markdownLiteralRes.type === "err") {
return {
type: "err",
err: `Invalid literal for variant \`${selectionChainToString(variant.selectionChain)}\` of key \`${id}\`:\n${markdownLiteralRes.err}`,
};
}
const markdownLiteral = markdownLiteralRes.ok;
const markdownRes = parseMarkdown(
markdownLiteral,
markdownSlots,
);
console.log(markdownRes);
@ -258,8 +274,22 @@ export function compileLocale<Config extends LocaleConfig>(
return {
type: "ok",
ok: (args: Record<string, FluentVariable> = {}) => {
const res = parseMarkdown(
const markdownLiteralRes = parseMessageLiteral(
"md",
bundle.formatPattern(pattern, args),
);
if (markdownLiteralRes.type === "err") {
// This should hopefully never happen since we've already
// verified all message variants parse as valid markdown above
throw new Error(
`Failed to parse markdown literal after compilation!\n${markdownLiteralRes.err}`,
);
}
const markdownLiteral = markdownLiteralRes.ok;
const res = parseMarkdown(
markdownLiteral,
markdownSlots,
);
@ -275,11 +305,64 @@ export function compileLocale<Config extends LocaleConfig>(
},
};
} else {
// typecheck string
// check if all variants are valid markdown
for (const variant of allVariants) {
const stringLiteralRes = parseMessageLiteral(
"string",
variant.string,
);
if (stringLiteralRes.type === "err") {
return {
type: "err",
err: `Invalid literal for variant \`${selectionChainToString(variant.selectionChain)}\` of key \`${id}\`:\n${stringLiteralRes.err}`,
};
}
const stringLiteral = stringLiteralRes.ok;
const stringRes = parseString(stringLiteral);
console.log(stringRes);
if (stringRes.type === "err") {
return {
type: "err",
err: `Invalid string for variant \`${selectionChainToString(variant.selectionChain)}\` of key \`${id}\`:\n${stringRes.err}`,
};
}
}
// TODO: will need to make sure markdown/slots are escapes when inserting variable values
return {
type: "ok",
ok: (args: Record<string, FluentVariable> = {}) =>
bundle.formatPattern(pattern, args),
ok: (args: Record<string, FluentVariable> = {}) => {
const stringLiteralRes = parseMessageLiteral(
"string",
bundle.formatPattern(pattern, args),
);
if (stringLiteralRes.type === "err") {
// This should hopefully never happen since we've already
// verified all message variants parse as valid strings above
throw new Error(
`Failed to parse string literal after compilation!\n${stringLiteralRes.err}`,
);
}
const stringLiteral = stringLiteralRes.ok;
const res = parseString(stringLiteral);
if (res.type === "err") {
// This should hopefully never happen since we've already
// verified all message variants parse as valid strings above
// TODO: no we dont, do that
throw new Error(
`Failed to parse string after compilation!\n${res.err}`,
);
}
return res.ok;
},
};
}
})();
@ -319,3 +402,71 @@ export function compileLocale<Config extends LocaleConfig>(
// SAFETY: validated above that all keys exist and are the correct type
return { type: "ok", ok: locale as InferLocale<Config> };
}
function parseMessageLiteral(
type: "string" | "md",
message: string,
): Result<string, string> {
const trimmedMessage = message.trim();
console.log(trimmedMessage);
const maybeStartIndexes: number[] = [];
const firstQuoteIndex = trimmedMessage.indexOf('"');
if (firstQuoteIndex !== -1) {
maybeStartIndexes.push(firstQuoteIndex);
}
const firstDashIndex = trimmedMessage.indexOf("-");
if (firstDashIndex !== -1) {
maybeStartIndexes.push(firstDashIndex);
}
const stringStartIndex = Math.min(...maybeStartIndexes);
console.log(stringStartIndex);
const actualPrefix = trimmedMessage.substring(0, stringStartIndex).trim();
const expectedPrefix = (() => {
switch (type) {
case "string": {
return "";
}
case "md": {
return "md";
}
}
})();
if (actualPrefix !== expectedPrefix) {
return {
type: "err",
err: `Expected prefix "${expectedPrefix}" for message with type \`${type}\`; Found: "${actualPrefix}"`,
};
}
return {
type: "ok",
ok: trimmedMessage.substring(actualPrefix.length).trim(),
};
}
function parseString(message: string): Result<string, string> {
const AFFIX = '"';
if (!message.startsWith(AFFIX)) {
return {
type: "err",
err: `String message expected to start with \`${AFFIX}\``,
};
}
if (!message.endsWith(AFFIX)) {
return {
type: "err",
err: `String message expected to end with \`${AFFIX}\``,
};
}
const deprefixed = message.substring(AFFIX.length);
const dequoted = deprefixed.substring(0, deprefixed.length - AFFIX.length);
return { type: "ok", ok: dequoted };
}

View file

@ -39,26 +39,39 @@ export function parseMarkdown<Slot extends string>(
markdownString: string,
slots: readonly Slot[],
): Result<Markdown<Slot>, string> {
if (markdownString === "|~") {
if (markdownString.trim() === "--") {
return { type: "ok", ok: { lines: [], slots: [] } };
}
const lines = markdownString.split("\n");
const strippedLines: string[] = [];
const dequotedLines: string[] = [];
for (const line of lines) {
const MARKDOWN_LINE_PREFIX = "| ";
if (!line.startsWith(MARKDOWN_LINE_PREFIX)) {
const MARKDOWN_LINE_AFFIX = '"';
if (!line.startsWith(MARKDOWN_LINE_AFFIX)) {
return {
type: "err",
err: `Line ${String(strippedLines.length + 1)} of markdown is not prefixed`,
err: `Line ${String(dequotedLines.length + 1)} of markdown must start with ${MARKDOWN_LINE_AFFIX}`,
};
}
strippedLines.push(line.substring(MARKDOWN_LINE_PREFIX.length));
if (!line.endsWith(MARKDOWN_LINE_AFFIX)) {
return {
type: "err",
err: `Line ${String(dequotedLines.length + 1)} of markdown must end with ${MARKDOWN_LINE_AFFIX}`,
};
}
const deprefixed = line.substring(MARKDOWN_LINE_AFFIX.length);
const dequoted = deprefixed.substring(
0,
deprefixed.length - MARKDOWN_LINE_AFFIX.length,
);
dequotedLines.push(dequoted);
}
const markdownLines: MarkdownLine<Slot>[] = [];
for (const line of strippedLines) {
for (const line of dequotedLines) {
const markdownLineRes = parseMarkdownLine(line, slots);
if (markdownLineRes.type === "err") {
return {

View file

@ -184,7 +184,15 @@ export function computeAllVariants(
break;
}
case "var": {
continue;
// used as a stand-in for runtime-provided values
const DUMMY_STRING = "$$$";
variants = variants.map((variant) => ({
selectionChain: variant.selectionChain,
string: variant.string + DUMMY_STRING,
}));
break;
}
case "str": {
variants = variants.map((variant) => ({