diff --git a/game/src/common/mod.rs b/game/src/common/mod.rs index 3706ba9d14..dc3c6cdddd 100644 --- a/game/src/common/mod.rs +++ b/game/src/common/mod.rs @@ -133,7 +133,7 @@ impl CommonState { let r = map.get_parent(l); osd.append_all(vec![ Line(format!("{} of ", map.get_l(l).lane_type.describe())), - Line(r.get_name()).fg(name_color), + Line(r.get_name(app.opts.language.as_ref())).fg(name_color), ]); if app.opts.dev { osd.append(Line(" (")); @@ -166,7 +166,7 @@ impl CommonState { let mut road_names = BTreeSet::new(); for r in &map.get_i(i).roads { - road_names.insert(map.get_r(*r).get_name()); + road_names.insert(map.get_r(*r).get_name(app.opts.language.as_ref())); } list_names(&mut osd, |l| l.fg(name_color), road_names); } @@ -218,7 +218,7 @@ impl CommonState { osd.append(Line(r.to_string()).fg(id_color)); osd.append(Line(" is ")); } - osd.append(Line(map.get_r(r).get_name()).fg(name_color)); + osd.append(Line(map.get_r(r).get_name(app.opts.language.as_ref())).fg(name_color)); } } osd diff --git a/game/src/common/navigate.rs b/game/src/common/navigate.rs index 44a5106de7..bb8c23441c 100644 --- a/game/src/common/navigate.rs +++ b/game/src/common/navigate.rs @@ -30,7 +30,7 @@ impl Navigator { .map .all_roads() .iter() - .map(|r| (r.get_name(), r.id)) + .map(|r| (r.get_name(app.opts.language.as_ref()), r.id)) .collect(), ) .named("street"), @@ -108,7 +108,7 @@ impl CrossStreet { // TODO This isn't so clear... txt.add(Line(format!( "(Or just quit to go to {})", - map.get_r(first[0]).get_name(), + map.get_r(first[0]).get_name(app.opts.language.as_ref()), ))); txt.draw(ctx) }, @@ -120,7 +120,7 @@ impl CrossStreet { ctx, cross_streets .into_iter() - .map(|r| (map.get_r(r).get_name(), r)) + .map(|r| (map.get_r(r).get_name(app.opts.language.as_ref()), r)) .collect(), ) .named("street"), diff --git a/game/src/devtools/mapping.rs b/game/src/devtools/mapping.rs index 3a7ca6ab2d..d98c968876 100644 --- a/game/src/devtools/mapping.rs +++ b/game/src/devtools/mapping.rs @@ -640,7 +640,8 @@ fn find_divided_highways(app: &App) -> HashSet { ]) .intersection(&map.get_r(r2).center_pts) .is_some() - && r1.get_name() == map.get_r(r2).get_name() + && r1.get_name(app.opts.language.as_ref()) + == map.get_r(r2).get_name(app.opts.language.as_ref()) { found.insert(r1.id); found.insert(r2); diff --git a/game/src/edit/lanes.rs b/game/src/edit/lanes.rs index 60d786dd26..5ef7c20e7a 100644 --- a/game/src/edit/lanes.rs +++ b/game/src/edit/lanes.rs @@ -75,9 +75,12 @@ impl LaneEditor { let parent = app.primary.map.get_parent(l); let col = vec![ - format!("Convert this lane of {} to what type?", parent.get_name()) - .draw_text(ctx) - .centered_horiz(), + format!( + "Convert this lane of {} to what type?", + parent.get_name(app.opts.language.as_ref()) + ) + .draw_text(ctx) + .centered_horiz(), Widget::custom_row(row).centered(), change_speed_limit(ctx, parent.speed_limit), Btn::text_fg("Change access restrictions").build_def(ctx, hotkey(Key::A)), diff --git a/game/src/edit/stop_signs.rs b/game/src/edit/stop_signs.rs index db340ac9c8..eda015b8cd 100644 --- a/game/src/edit/stop_signs.rs +++ b/game/src/edit/stop_signs.rs @@ -210,7 +210,13 @@ impl State for StopSignEditor { let mut osd = Text::new(); osd.add_appended(vec![ Line("Stop sign for "), - Line(app.primary.map.get_r(r).get_name()).fg(app.cs.bottom_bar_name), + Line( + app.primary + .map + .get_r(r) + .get_name(app.opts.language.as_ref()), + ) + .fg(app.cs.bottom_bar_name), ]); CommonState::draw_custom_osd(g, app, osd); } else { diff --git a/game/src/edit/traffic_signals/mod.rs b/game/src/edit/traffic_signals/mod.rs index 343f7afffb..de12f9e1a5 100644 --- a/game/src/edit/traffic_signals/mod.rs +++ b/game/src/edit/traffic_signals/mod.rs @@ -478,13 +478,22 @@ impl State for TrafficSignalEditor { let osd = if id.crosswalk { Text::from(Line(format!( "Crosswalk across {}", - app.primary.map.get_r(id.from.id).get_name() + app.primary + .map + .get_r(id.from.id) + .get_name(app.opts.language.as_ref()) ))) } else { Text::from(Line(format!( "Turn from {} to {}", - app.primary.map.get_r(id.from.id).get_name(), - app.primary.map.get_r(id.to.id).get_name() + app.primary + .map + .get_r(id.from.id) + .get_name(app.opts.language.as_ref()), + app.primary + .map + .get_r(id.to.id) + .get_name(app.opts.language.as_ref()) ))) }; CommonState::draw_custom_osd(g, app, osd); @@ -564,7 +573,12 @@ fn make_side_panel( let mut road_names = BTreeSet::new(); for r in &app.primary.map.get_i(i).roads { - road_names.insert(app.primary.map.get_r(*r).get_name()); + road_names.insert( + app.primary + .map + .get_r(*r) + .get_name(app.opts.language.as_ref()), + ); } for r in road_names { txt.add(Line(format!("- {}", r))); diff --git a/game/src/info/bus.rs b/game/src/info/bus.rs index 2304447c81..720446db8f 100644 --- a/game/src/info/bus.rs +++ b/game/src/info/bus.rs @@ -237,7 +237,7 @@ pub fn route(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusRouteI rows.push(format!("{} stops", route.stops.len()).draw_text(ctx)); { let i = map.get_i(map.get_l(route.start).src_i); - let name = format!("Starts at {}", i.name(map)); + let name = format!("Starts at {}", i.name(app.opts.language.as_ref(), map)); rows.push(Widget::row(vec![ Btn::svg( "system/assets/timeline/goal_pos.svg", @@ -273,7 +273,7 @@ pub fn route(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusRouteI } if let Some(l) = route.end_border { let i = map.get_i(map.get_l(l).dst_i); - let name = format!("Ends at {}", i.name(map)); + let name = format!("Ends at {}", i.name(app.opts.language.as_ref(), map)); rows.push(Widget::row(vec![ Btn::svg( "system/assets/timeline/goal_pos.svg", diff --git a/game/src/info/intersection.rs b/game/src/info/intersection.rs index 39ecffe54d..4d23960d9c 100644 --- a/game/src/info/intersection.rs +++ b/game/src/info/intersection.rs @@ -20,7 +20,12 @@ pub fn info(ctx: &EventCtx, app: &App, details: &mut Details, id: IntersectionID let mut txt = Text::from(Line("Connecting")); let mut road_names = BTreeSet::new(); for r in &i.roads { - road_names.insert(app.primary.map.get_r(*r).get_name()); + road_names.insert( + app.primary + .map + .get_r(*r) + .get_name(app.opts.language.as_ref()), + ); } for r in road_names { // TODO The spacing is ignored, so use - diff --git a/game/src/info/lane.rs b/game/src/info/lane.rs index 805074e19c..d37201899f 100644 --- a/game/src/info/lane.rs +++ b/game/src/info/lane.rs @@ -222,7 +222,7 @@ fn header(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID, tab: Tab .draw(ctx), header_btns(ctx), ])); - rows.push(format!("@ {}", r.get_name()).draw_text(ctx)); + rows.push(format!("@ {}", r.get_name(app.opts.language.as_ref())).draw_text(ctx)); let mut tabs = vec![("Info", Tab::LaneInfo(id))]; if !l.is_parking() { diff --git a/game/src/info/trip.rs b/game/src/info/trip.rs index 836bd1dc33..fc4d4f4905 100644 --- a/game/src/info/trip.rs +++ b/game/src/info/trip.rs @@ -160,8 +160,8 @@ pub fn future( )); } else { // TODO Warp buttons. make_table is showing its age. - let (id1, _, name1) = endpoint(&trip.start, &app.primary.map); - let (id2, _, name2) = endpoint(&trip.end, &app.primary.map); + let (id1, _, name1) = endpoint(&trip.start, app); + let (id2, _, name2) = endpoint(&trip.end, app); details .warpers .insert(format!("jump to start of {}", id), id1); @@ -323,8 +323,8 @@ pub fn aborted(ctx: &mut EventCtx, app: &App, id: TripID) -> Widget { .draw(ctx)]; // TODO Warp buttons. make_table is showing its age. - let (_, _, name1) = endpoint(&trip.start, &app.primary.map); - let (_, _, name2) = endpoint(&trip.end, &app.primary.map); + let (_, _, name1) = endpoint(&trip.start, app); + let (_, _, name2) = endpoint(&trip.end, app); col.extend(make_table( ctx, vec![ @@ -344,8 +344,8 @@ pub fn cancelled(ctx: &mut EventCtx, app: &App, id: TripID) -> Widget { let mut col = vec!["Trip cancelled due to traffic pattern modifications".draw_text(ctx)]; // TODO Warp buttons. make_table is showing its age. - let (_, _, name1) = endpoint(&trip.start, &app.primary.map); - let (_, _, name2) = endpoint(&trip.end, &app.primary.map); + let (_, _, name1) = endpoint(&trip.start, app); + let (_, _, name2) = endpoint(&trip.end, app); col.extend(make_table( ctx, vec![ @@ -374,7 +374,7 @@ fn make_timeline( let end_time = phases.last().as_ref().and_then(|p| p.end_time); let start_btn = { - let (id, center, name) = endpoint(&trip.start, map); + let (id, center, name) = endpoint(&trip.start, app); details .warpers .insert(format!("jump to start of {}", trip_id), id); @@ -419,7 +419,7 @@ fn make_timeline( }; let goal_btn = { - let (id, center, name) = endpoint(&trip.end, map); + let (id, center, name) = endpoint(&trip.end, app); details .warpers .insert(format!("jump to goal of {}", trip_id), id); @@ -699,18 +699,21 @@ fn make_elevation(ctx: &EventCtx, color: Color, walking: bool, path: &Path, map: } // (ID, center, name) -fn endpoint(endpt: &TripEndpoint, map: &Map) -> (ID, Pt2D, String) { +fn endpoint(endpt: &TripEndpoint, app: &App) -> (ID, Pt2D, String) { match endpt { TripEndpoint::Bldg(b) => { - let bldg = map.get_b(*b); + let bldg = app.primary.map.get_b(*b); (ID::Building(*b), bldg.label_center, bldg.address.clone()) } TripEndpoint::Border(i, _) => { - let i = map.get_i(*i); + let i = app.primary.map.get_i(*i); ( ID::Intersection(i.id), i.polygon.center(), - format!("off map, via {}", i.name(map)), + format!( + "off map, via {}", + i.name(app.opts.language.as_ref(), &app.primary.map) + ), ) } } diff --git a/game/src/options.rs b/game/src/options.rs index c688c56960..b9648af33f 100644 --- a/game/src/options.rs +++ b/game/src/options.rs @@ -22,6 +22,8 @@ pub struct Options { pub time_increment: Duration, pub resume_after_edit: bool, pub dont_draw_time_warp: bool, + + pub language: Option, } impl Options { @@ -39,6 +41,8 @@ impl Options { time_increment: Duration::minutes(10), resume_after_edit: true, dont_draw_time_warp: false, + + language: None, } } } @@ -167,6 +171,17 @@ impl OptionsPanel { None, app.opts.large_unzoomed_agents, ), + Widget::row(vec![ + "Language".draw_text(ctx), + Widget::dropdown(ctx, "language", app.opts.language.clone(), { + let mut choices = Vec::new(); + choices.push(Choice::new("Map native language", None)); + for lang in app.primary.map.get_languages() { + choices.push(Choice::new(lang, Some(lang.to_string()))); + } + choices + }), + ]), ]) .bg(app.cs.section_bg) .padding(8), @@ -222,7 +237,7 @@ impl State for OptionsPanel { if app.opts.traffic_signal_style != style { app.opts.traffic_signal_style = style; println!("Rerendering traffic signals..."); - for i in app.primary.draw_map.intersections.iter_mut() { + for i in &mut app.primary.draw_map.intersections { *i.draw_traffic_signal.borrow_mut() = None; } } @@ -237,6 +252,14 @@ impl State for OptionsPanel { app.opts.large_unzoomed_agents = self.composite.is_checked("Draw enlarged unzoomed agents"); + let language = self.composite.dropdown_value("language"); + if language != app.opts.language { + app.opts.language = language; + for r in &mut app.primary.draw_map.roads { + r.clear_rendering(); + } + } + return Transition::Pop; } _ => unreachable!(), diff --git a/game/src/render/road.rs b/game/src/render/road.rs index 925406f77f..352e8504fb 100644 --- a/game/src/render/road.rs +++ b/game/src/render/road.rs @@ -26,6 +26,7 @@ impl DrawRoad { pub fn clear_rendering(&mut self) { *self.draw_center_line.borrow_mut() = None; + *self.label.borrow_mut() = None; } } @@ -73,7 +74,7 @@ impl Renderable for DrawRoad { let mut batch = GeomBatch::new(); let r = app.primary.map.get_r(self.id); if !r.is_light_rail() { - let name = r.get_name(); + let name = r.get_name(app.opts.language.as_ref()); if r.center_pts.length() >= Distance::meters(30.0) && name != "???" { // TODO If it's definitely straddling bus/bike lanes, change the color? Or // even easier, just skip the center lines? diff --git a/game/src/sandbox/misc_tools.rs b/game/src/sandbox/misc_tools.rs index 599b75449f..d2c07cafd3 100644 --- a/game/src/sandbox/misc_tools.rs +++ b/game/src/sandbox/misc_tools.rs @@ -163,7 +163,10 @@ impl TurnExplorer { Text::from( Line(format!( "Turns from {}", - app.primary.map.get_parent(l).get_name() + app.primary + .map + .get_parent(l) + .get_name(app.opts.language.as_ref()) )) .small_heading(), ) diff --git a/map_model/src/make/buildings.rs b/map_model/src/make/buildings.rs index 161d44c74b..e969de1fe2 100644 --- a/map_model/src/make/buildings.rs +++ b/map_model/src/make/buildings.rs @@ -105,7 +105,7 @@ fn get_address(tags: &Tags, sidewalk: LaneID, map: &Map) -> String { match (tags.get("addr:housenumber"), tags.get("addr:street")) { (Some(num), Some(st)) => format!("{} {}", num, st), (None, Some(st)) => format!("??? {}", st), - _ => format!("??? {}", map.get_parent(sidewalk).get_name()), + _ => format!("??? {}", map.get_parent(sidewalk).get_name(None)), } } diff --git a/map_model/src/make/mod.rs b/map_model/src/make/mod.rs index fa11ec02d8..39c550af53 100644 --- a/map_model/src/make/mod.rs +++ b/map_model/src/make/mod.rs @@ -197,7 +197,7 @@ impl Map { biking_blackhole: false, }); } - if road.get_name() == "???" { + if road.get_name(None) == "???" { // Suppress the warning in some cases. if !(road.osm_tags.is("noname", "yes") || road diff --git a/map_model/src/make/traffic_signals.rs b/map_model/src/make/traffic_signals.rs index 5698ada555..762afead22 100644 --- a/map_model/src/make/traffic_signals.rs +++ b/map_model/src/make/traffic_signals.rs @@ -26,7 +26,7 @@ pub fn get_possible_policies( timer.error(format!( "seattle_traffic_signals data for {} ({}) out of date, go update it", i.orig_id, - i.name(map) + i.name(None, map) )); } } diff --git a/map_model/src/map.rs b/map_model/src/map.rs index 963a6a8d35..788bb39b35 100644 --- a/map_model/src/map.rs +++ b/map_model/src/map.rs @@ -633,4 +633,16 @@ impl Map { self.bus_routes[br.0].orig_spawn_times = times.clone(); self.bus_routes[br.0].spawn_times = times; } + + pub fn get_languages(&self) -> BTreeSet<&str> { + let mut languages = BTreeSet::new(); + for r in self.all_roads() { + for key in r.osm_tags.inner().keys() { + if let Some(x) = key.strip_prefix("name:") { + languages.insert(x); + } + } + } + languages + } } diff --git a/map_model/src/objects/intersection.rs b/map_model/src/objects/intersection.rs index 922ca7b61e..739e739432 100644 --- a/map_model/src/objects/intersection.rs +++ b/map_model/src/objects/intersection.rs @@ -142,11 +142,11 @@ impl Intersection { .map(|l| map.get_l(*l).get_directed_parent(map)) } - pub fn name(&self, map: &Map) -> String { + pub fn name(&self, lang: Option<&String>, map: &Map) -> String { let road_names = self .roads .iter() - .map(|r| map.get_r(*r).get_name()) + .map(|r| map.get_r(*r).get_name(lang)) .collect::>(); abstutil::plain_list_names(road_names) } diff --git a/map_model/src/objects/road.rs b/map_model/src/objects/road.rs index 398fe8490f..8ea202aefe 100644 --- a/map_model/src/objects/road.rs +++ b/map_model/src/objects/road.rs @@ -293,7 +293,13 @@ impl Road { .make_polygons(self.get_half_width(map) * 2.0) } - pub fn get_name(&self) -> String { + pub fn get_name(&self, lang: Option<&String>) -> String { + if let Some(lang) = lang { + if let Some(name) = self.osm_tags.get(&format!("name:{}", lang)) { + return name.to_string(); + } + } + if let Some(name) = self.osm_tags.get(osm::NAME) { if name == "" { return "???".to_string();