-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: Translatable diagnostics #19545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Brayan-724
wants to merge
2
commits into
rust-lang:master
Choose a base branch
from
RustLangES:feat/use-fluent
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
[package] | ||
name = "fluent-build" | ||
version = "0.0.0" | ||
repository.workspace = true | ||
description = "Utils for build-time support for fluent files" | ||
|
||
authors.workspace = true | ||
edition.workspace = true | ||
license.workspace = true | ||
rust-version.workspace = true | ||
|
||
[lib] | ||
|
||
[dependencies] | ||
fluent-syntax.workspace = true | ||
proc-macro2.workspace = true | ||
quote.workspace = true | ||
|
||
# local deps | ||
|
||
[dev-dependencies] | ||
expect-test = "1.5.1" | ||
|
||
# local deps | ||
test-utils.workspace = true | ||
test-fixture.workspace = true | ||
|
||
[lints] | ||
workspace = true | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
use std::io; | ||
|
||
pub type FluentParserError = | ||
(fluent_syntax::ast::Resource<String>, Vec<fluent_syntax::parser::ParserError>); | ||
|
||
#[derive(Debug)] | ||
pub enum FluentBuildError { | ||
Io(io::Error), | ||
Parser(FluentParserError), | ||
/// Panic errors | ||
Custom(String), | ||
} | ||
|
||
impl From<io::Error> for FluentBuildError { | ||
fn from(value: io::Error) -> Self { | ||
Self::Io(value) | ||
} | ||
} | ||
|
||
impl From<FluentParserError> for FluentBuildError { | ||
fn from(value: FluentParserError) -> Self { | ||
Self::Parser(value) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
pub mod error; | ||
|
||
use std::path::PathBuf; | ||
use std::{env, fs, io}; | ||
|
||
use error::FluentBuildError; | ||
use fluent_syntax::ast; | ||
use proc_macro2::TokenStream; | ||
use quote::{format_ident, quote}; | ||
|
||
fn get_cargo_manifest() -> String { | ||
env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| panic!("Running on non cargo environment")) | ||
} | ||
|
||
pub fn build_fluent_src() -> io::Result<()> { | ||
let mut output = "//! This file is generated by `fluent-build`".to_owned(); | ||
|
||
for entry in fs::read_dir("src")? { | ||
let entry = entry?.path(); | ||
|
||
if !entry.extension().is_none_or(|e| e.to_string_lossy().to_string() == "ftl") { | ||
continue; | ||
} | ||
|
||
let output_mod = entry | ||
.file_stem() | ||
.unwrap() | ||
.to_string_lossy() | ||
.to_string() | ||
.replace("-", "_") | ||
.replace(".", "_"); | ||
let output_file = | ||
entry.with_extension("rs").file_name().unwrap().to_string_lossy().to_string(); | ||
|
||
output += | ||
&format!("\n#[path = \"{output_file}\"]\nmod {output_mod};\npub use {output_mod}::*;"); | ||
|
||
build_fluent_file(entry).map_err(|e| match e { | ||
FluentBuildError::Io(error) => return error, | ||
FluentBuildError::Parser(_) => todo!("parse error"), | ||
FluentBuildError::Custom(c) => panic!("{c}"), | ||
})?; | ||
} | ||
|
||
let absolute_path = PathBuf::from(get_cargo_manifest()).join("src/generated.rs"); | ||
fs::write(absolute_path, output)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn build_fluent_file(relative_path: PathBuf) -> Result<(), FluentBuildError> { | ||
let absolute_path = PathBuf::from(get_cargo_manifest()).join(relative_path); | ||
let resource = fs::read_to_string(&absolute_path) | ||
.map_err(|e| FluentBuildError::Custom(format!("{absolute_path:#?}: {e}")))?; | ||
|
||
for esc in ["\\n", "\\\"", "\\'"] { | ||
if resource.contains(esc) { | ||
return Err(FluentBuildError::Custom(format!( | ||
"invalid escape `{esc}` in Fluent resource.\nFluent does not interpret these escape sequences (<https://projectfluent.org/fluent/guide/special.html>)" | ||
))); | ||
} | ||
} | ||
|
||
// Use parse_runtime because we don't need debug info | ||
let resource = fluent_syntax::parser::parse_runtime(resource)?.body; | ||
|
||
let entries = resource | ||
.into_iter() | ||
.filter_map(|entry| { | ||
let (name, value, attributes, comment) = match entry { | ||
ast::Entry::Message(ast::Message { | ||
id, | ||
value: Some(value), | ||
attributes, | ||
comment, | ||
}) => (id.name, value, attributes, comment), | ||
ast::Entry::Term(ast::Term { id, value, attributes, comment }) => { | ||
(id.name, value, attributes, comment) | ||
} | ||
_ => return None, | ||
}; | ||
|
||
let children = attributes | ||
.into_iter() | ||
.map(|ast::Attribute { id, value }| { | ||
entry_to_code(format!("{name}-{}", id.name), value, None) | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
let mut code = entry_to_code(name, value, comment).to_string(); | ||
|
||
for child in children { | ||
code.push('\n'); | ||
code += &child.to_string(); | ||
} | ||
|
||
Some(code) | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
let code = entries.join("\n"); | ||
let result_file = absolute_path.with_extension("rs"); | ||
|
||
fs::write(result_file, code)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn entry_to_code( | ||
name: String, | ||
pattern: ast::Pattern<String>, | ||
comment: Option<ast::Comment<String>>, | ||
) -> TokenStream { | ||
let name = format_ident!("{}", name.replace("-", "_")); | ||
|
||
let is_dynamic = | ||
pattern.elements.iter().any(|elm| matches!(elm, ast::PatternElement::Placeable { .. })); | ||
|
||
let comment = comment | ||
.map(|comment| { | ||
let content = comment.content.into_iter().map(|c| quote! {#[doc = #c]}); | ||
quote! {#(#content)*} | ||
}) | ||
.unwrap_or(quote! {}); | ||
|
||
if is_dynamic { | ||
let (args, format_str) = pattern.elements.into_iter().fold( | ||
(Vec::new(), String::new()), | ||
|(mut args, mut body), elm| { | ||
resolve_pattern_element(&mut args, &mut body, elm); | ||
(args, body) | ||
}, | ||
); | ||
|
||
quote! { | ||
#comment | ||
pub fn #name(#(#args),*) -> String { | ||
format!(#format_str) | ||
} | ||
} | ||
} else { | ||
// Collect all static elements | ||
let body = pattern | ||
.elements | ||
.into_iter() | ||
.filter_map(|elm| match elm { | ||
ast::PatternElement::TextElement { value } => Some(value), | ||
_ => None, | ||
}) | ||
.collect::<String>(); | ||
|
||
quote! { | ||
#comment | ||
#[allow(non_upper_case_globals)] | ||
pub const #name: &str = #body; | ||
} | ||
} | ||
} | ||
|
||
fn resolve_pattern_element( | ||
args: &mut Vec<TokenStream>, | ||
body: &mut String, | ||
elm: ast::PatternElement<String>, | ||
) { | ||
match elm { | ||
ast::PatternElement::TextElement { value } => { | ||
*body += &value; | ||
} | ||
ast::PatternElement::Placeable { expression } => match expression { | ||
ast::Expression::Select { .. } => todo!(), | ||
ast::Expression::Inline(inline_expression) => match inline_expression { | ||
ast::InlineExpression::StringLiteral { .. } => todo!(), | ||
ast::InlineExpression::NumberLiteral { .. } => todo!(), | ||
ast::InlineExpression::FunctionReference { .. } => todo!(), | ||
ast::InlineExpression::MessageReference { .. } => todo!(), | ||
ast::InlineExpression::TermReference { .. } => todo!(), | ||
ast::InlineExpression::VariableReference { id } => { | ||
let arg = format_ident!("{}", id.name); | ||
args.push(quote! { #arg : impl ::std::fmt::Display }); | ||
|
||
body.push('{'); | ||
body.push_str(&id.name); | ||
body.push('}'); | ||
} | ||
ast::InlineExpression::Placeable { .. } => todo!("Expression inception"), | ||
}, | ||
}, | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
src/*.rs | ||
!src/lib.rs |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
[package] | ||
name = "fluent-files" | ||
version = "0.0.0" | ||
repository.workspace = true | ||
description = "Collection of translated files" | ||
|
||
authors.workspace = true | ||
edition.workspace = true | ||
license.workspace = true | ||
rust-version.workspace = true | ||
|
||
[lib] | ||
|
||
[build-dependencies] | ||
# local deps | ||
fluent-build.workspace = true | ||
|
||
[dev-dependencies] | ||
expect-test = "1.5.1" | ||
|
||
# local deps | ||
test-utils.workspace = true | ||
test-fixture.workspace = true | ||
|
||
[lints] | ||
workspace = true | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
fn main() { | ||
println!("cargo::rerun-if-changed=src/"); | ||
let result = fluent_build::build_fluent_src(); | ||
if let Err(r) = result { | ||
panic!("{r:#?}") | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
await-outside-of-async-code = E0728 | ||
await-outside-of-async = `await` is used inside {$ctx}, which is not an `async` context |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
mod generated; | ||
pub use generated::*; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rustc errors do not change with translations