From d34ab1e1fea959be04f968af5311061efd38471e Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 18 Dec 2014 21:40:49 +0300 Subject: [PATCH] langs improved for future translations, win version only for now --- Telegram/Resources/lang.strings | 482 +++++++++++++++++ Telegram/Resources/lang.txt | 538 ------------------- Telegram/SourceFiles/_other/genlang.cpp | 518 +++++++++++++----- Telegram/SourceFiles/_other/mlmain.cpp | 2 +- Telegram/SourceFiles/app.cpp | 42 +- Telegram/SourceFiles/application.cpp | 13 +- Telegram/SourceFiles/application.h | 2 +- Telegram/SourceFiles/boxes/aboutbox.cpp | 2 +- Telegram/SourceFiles/boxes/addcontactbox.cpp | 4 +- Telegram/SourceFiles/dialogswidget.cpp | 2 +- Telegram/SourceFiles/gui/text.cpp | 12 +- Telegram/SourceFiles/gui/text.h | 3 + Telegram/SourceFiles/history.cpp | 112 ++-- Telegram/SourceFiles/history.h | 6 +- Telegram/SourceFiles/historywidget.cpp | 25 +- Telegram/SourceFiles/intro/introcode.cpp | 4 +- Telegram/SourceFiles/intro/introphone.cpp | 6 +- Telegram/SourceFiles/lang.cpp | 69 +++ Telegram/SourceFiles/lang.h | 118 ++++ Telegram/SourceFiles/langloaderplain.cpp | 171 ++++-- Telegram/SourceFiles/langloaderplain.h | 4 + Telegram/SourceFiles/mainwidget.cpp | 30 +- Telegram/SourceFiles/mediaview.cpp | 8 +- Telegram/SourceFiles/overviewwidget.cpp | 4 +- Telegram/SourceFiles/profilewidget.cpp | 29 +- Telegram/SourceFiles/pspecific_mac_p.mm | 38 +- Telegram/SourceFiles/settings.cpp | 4 +- Telegram/SourceFiles/settings.h | 2 + Telegram/SourceFiles/settingswidget.cpp | 14 +- Telegram/SourceFiles/title.cpp | 1 - Telegram/SourceFiles/window.cpp | 2 +- Telegram/Telegram.vcxproj | 14 +- Telegram/Telegram.vcxproj.filters | 20 +- 33 files changed, 1374 insertions(+), 927 deletions(-) create mode 100644 Telegram/Resources/lang.strings delete mode 100644 Telegram/Resources/lang.txt create mode 100644 Telegram/SourceFiles/lang.cpp create mode 100644 Telegram/SourceFiles/lang.h diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings new file mode 100644 index 0000000000..c8ba0eb4fd --- /dev/null +++ b/Telegram/Resources/lang.strings @@ -0,0 +1,482 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014 John Preston, https://desktop.telegram.org +*/ +"lng_maintitle" = "Telegram Desktop"; + +"lng_menu_contacts" = "Contacts"; +"lng_menu_settings" = "Settings"; +"lng_menu_about" = "About"; +"lng_menu_update" = "Update"; +"lng_menu_restart" = "Restart"; +"lng_menu_back" = "Back"; + +"lng_open_from_tray" = "Open Telegram"; +"lng_minimize_to_tray" = "Minimize to tray"; +"lng_quit_from_tray" = "Quit Telegram"; +"lng_tray_icon_text" = "Telegram is still running here,\nyou can change this from settings page.\n\nIf this icon disappears from tray menu,\nyou can drag it back here from hidden icons."; + +"lng_month1" = "January"; +"lng_month2" = "February"; +"lng_month3" = "March"; +"lng_month4" = "April"; +"lng_month5" = "May"; +"lng_month6" = "June"; +"lng_month7" = "July"; +"lng_month8" = "August"; +"lng_month9" = "September"; +"lng_month10" = "October"; +"lng_month11" = "November"; +"lng_month12" = "December"; + +"lng_weekday1" = "Mon"; +"lng_weekday2" = "Tue"; +"lng_weekday3" = "Wed"; +"lng_weekday4" = "Thu"; +"lng_weekday5" = "Fri"; +"lng_weekday6" = "Sat"; +"lng_weekday7" = "Sun"; + +"lng_weekday1_full" = "Monday"; +"lng_weekday2_full" = "Tuesday"; +"lng_weekday3_full" = "Wednesday"; +"lng_weekday4_full" = "Thursday"; +"lng_weekday5_full" = "Friday"; +"lng_weekday6_full" = "Saturday"; +"lng_weekday7_full" = "Sunday"; + +"lng_month_day" = "{month} {day}"; + +"lng_cancel" = "Cancel"; +"lng_continue" = "Continue"; +"lng_close" = "Close"; +"lng_connecting" = "Connecting.."; +"lng_reconnecting" = "Reconnect {count:now|in # s|in # s}.."; +"lng_reconnecting_try_now" = "Try now"; + +"lng_status_service_notifications" = "service notifications"; +"lng_status_offline" = "last seen a long time ago"; +"lng_status_recently" = "last seen recently"; +"lng_status_last_week" = "last seen within a week"; +"lng_status_last_month" = "last seen within a month"; +"lng_status_invisible" = "invisible"; +"lng_status_lastseen_now" = "last seen just now"; +"lng_status_lastseen_minutes" = "last seen {count:_not_used_|# minute|# minutes} ago"; +"lng_status_lastseen_hours" = "last seen {count:_not_used_|# hour|# hours} ago"; +"lng_status_lastseen_today" = "last seen today at {time}"; +"lng_status_lastseen_yesterday" = "last seen yesterday at {time}"; +"lng_status_lastseen_date" = "last seen {date}"; +"lng_status_lastseen_date_time" = "last seen {date} at {time}"; +"lng_status_online" = "online"; +"lng_status_connecting" = "connecting.."; + +"lng_chat_status_unaccessible" = "group is unaccessible"; +"lng_chat_status_members" = "{count:no members|# member|# members}"; +"lng_chat_status_members_online" = "{count:_not_used_|# member|# members}, {count_online:_not_used_|# online|# online}"; + +"lng_server_error" = "Internal server error."; +"lng_flood_error" = "Too much tries. Please try again later."; +"lng_deleted" = "Unknown"; + +"lng_intro" = "Welcome to the official [a href=\"https://telegram.org/\"]Telegram[/a] desktop app.\nIt's [b]fast[/b] and [b]secure[/b]."; +"lng_start_msgs" = "START MESSAGING"; + +"lng_intro_next" = "NEXT"; +"lng_intro_finish" = "SIGN UP"; + +"lng_phone_ph" = "Your phone number"; +"lng_phone_title" = "Your Phone"; +"lng_phone_desc" = "Please confirm your country code and\nenter your phone number."; +"lng_phone_notreg" = "Note: if you don't have a Telegram account yet,\nplease [b]sign up[/b] with your [a href=\"https://telegram.org/\"]iOS / Android[/a] or {signup_start}here »{signup_end}"; +"lng_country_code" = "Country Code"; +"lng_bad_country_code" = "Invalid Country Code"; +"lng_country_ph" = "Search"; +"lng_country_done" = "Done"; +"lng_country_none" = "Country not found"; +"lng_country_select" = "Select Country"; + +"lng_code_ph" = "Your code"; +"lng_code_desc" = "We have sent you a message with activation\ncode to your phone. Please enter it below."; +"lng_code_call" = "Telegram will dial your number in {minutes}:{seconds}"; +"lng_code_calling" = "Requesting a call from Telegram.."; +"lng_code_called" = "Telegram dialed your number"; + +"lng_bad_phone" = "Invalid phone number. Please try again."; +"lng_bad_phone_noreg" = "Phone number not registered."; +"lng_bad_code" = "You have entered an invalid code. Please try again."; +"lng_bad_name" = "Please enter your first and last name."; +"lng_bad_chat_title" = "Please enter new chat title."; +"lng_bad_photo" = "Bad image selected."; + +"lng_signup_title" = "Information and photo"; +"lng_signup_desc" = "Please enter your name and\nupload a photo."; +"lng_signup_firstname" = "First Name"; +"lng_signup_lastname" = "Last Name"; + +"lng_dlg_filter" = "Search"; +"lng_dlg_conversations" = "Conversations"; +"lng_dlg_messages" = "Messages"; +"lng_dlg_new_group_name" = "Group name"; +"lng_dlg_create_group" = "Create"; +"lng_no_contacts" = "You have no contacts"; +"lng_contacts_loading" = "Loading.."; +"lng_contacts_not_found" = "No contacts found"; + +"lng_settings_profile" = "Profile"; +"lng_settings_edit" = "Edit"; +"lng_settings_save" = "Save"; +"lng_settings_cancel" = "Cancel"; +"lng_settings_upload" = "Set Profile Photo"; +"lng_settings_badsize" = "This image has bad size, please try other."; +"lng_settings_crop_profile" = "Select square area for your profile photo"; +"lng_settings_uploading_photo" = "Uploading photo.."; + +"lng_username_title" = "Change username"; +"lng_username_about" = "You can choose a username on Telegram.\nIf you do, other people will be able to find\nyou by this username and contact you\nwithout knowing your phone number.\n\nYou can use a-z, 0-9 and underscores.\nMinimum length is 5 characters."; +"lng_username_invalid" = "This name is invalid."; +"lng_username_occupied" = "This name is already occupied."; +"lng_username_too_short" = "This name is too short."; +"lng_username_bad_symbols" = "This name has bad symbols."; +"lng_username_available" = "This name is available."; +"lng_username_not_found" = "User @{user} not found."; + +"lng_settings_section_contact_info" = "Contact info"; +"lng_settings_phone_number" = "Phone number:"; +"lng_settings_username" = "Username:"; +"lng_settings_choose_username" = "choose username"; +"lng_settings_change_username" = "Change"; + +"lng_settings_section_notify" = "Notifications"; +"lng_settings_desktop_notify" = "Desktop notifications"; +"lng_settings_show_name" = "Show sender's name"; +"lng_settings_show_preview" = "Show message preview"; +"lng_settings_sound_notify" = "Play sound"; + +"lng_notification_title" = "Telegram Desktop"; +"lng_notification_preview" = "You have a new message"; + +"lng_settings_section_general" = "General"; +"lng_settings_auto_update" = "Update automatically"; +"lng_settings_current_version" = "Version {version}"; +"lng_settings_check_now" = "Check for updates"; +"lng_settings_update_checking" = "Checking for updates.."; +"lng_settings_latest_installed" = "Latest version is installed"; +"lng_settings_downloading" = "Downloading update {ready} / {total} Mb.."; +"lng_settings_update_ready" = "New version is ready"; +"lng_settings_update_now" = "Restart Now"; +"lng_settings_update_fail" = "Update check failed :("; +"lng_settings_workmode_tray" = "Show tray icon"; +"lng_settings_workmode_window" = "Show taskbar icon"; +"lng_settings_auto_start" = "Launch Telegram when system starts"; +"lng_settings_start_min" = "Launch minimized"; +"lng_settings_add_sendto" = "Place Telegram in «Send to» menu"; +"lng_settings_scale_label" = "Interface scale"; +"lng_settings_scale_auto" = "Auto ({cur})"; + +"lng_settings_section_chat" = "Chat options"; +"lng_settings_replace_emojis" = "Replace emojis"; +"lng_settings_view_emojis" = "View list"; +"lng_settings_emoji_list" = "List of supported emojis"; +"lng_settings_send_enter" = "Send by Enter"; +"lng_settings_send_ctrlenter" = "Send by Ctrl+Enter"; +"lng_settings_send_cmdenter" = "Send by Cmd+Enter"; +"lng_settings_cats_and_dogs" = "Allow cats and dogs"; + +"lng_download_path_dont_ask" = "Don't ask download path for each file"; +"lng_download_path_label" = "Download path: "; +"lng_download_path_temp" = "temp folder"; +"lng_download_path_default" = "default folder"; +"lng_download_path_clear" = "Clear All"; +"lng_download_path_header" = "Choose download path"; +"lng_download_path_default_radio" = "Telegram folder in system «Downloads»"; +"lng_download_path_temp_radio" = "Temp folder, cleared on logout or uninstall"; +"lng_download_path_dir_radio" = "Custom folder, cleared only manually"; +"lng_download_path_choose" = "Choose download path"; +"lng_sure_clear_downloads" = "Do you want to remove all downloaded files from temp folder? It is done automatically on logout or program uninstall."; +"lng_download_path_failed" = "File download could not be started. It could happen because of a bad download location.\n\nYou can change download path in Settings."; +"lng_download_path_settings" = "Go to Settings"; +"lng_download_finish_failed" = "File download could not be finished.\n\nWould you like to try again?"; +"lng_download_path_clearing" = "Clearing.."; +"lng_download_path_cleared" = "Cleared!"; +"lng_download_path_clear_failed" = "Clear failed :("; + +"lng_settings_section_cache" = "Local storage"; +"lng_settings_no_images_cached" = "No cached images found!"; +"lng_settings_images_cached" = "Cached: {count:_not_used_|# image|# images}, {size}"; +"lng_local_images_clear" = "Clear All"; +"lng_local_images_clearing" = "Clearing.."; +"lng_local_images_cleared" = "Cleared!"; +"lng_local_images_clear_failed" = "Clear failed :("; + +"lng_settings_section_advanced" = "Advanced"; +"lng_connection_type" = "Connection type:"; +"lng_connection_auto_connecting" = "Default (connecting..)"; +"lng_connection_auto" = "Default ({type} used)"; +"lng_connection_http_proxy" = "HTTP with proxy"; +"lng_connection_tcp_proxy" = "TCP with proxy"; +"lng_connection_header" = "Connection type"; +"lng_connection_auto_rb" = "Auto (TCP if available or HTTP)"; +"lng_connection_http_proxy_rb" = "HTTP with custom http-proxy"; +"lng_connection_tcp_proxy_rb" = "TCP with custom socks5-proxy"; +"lng_connection_host_ph" = "Hostname"; +"lng_connection_port_ph" = "Port"; +"lng_connection_user_ph" = "Username"; +"lng_connection_password_ph" = "Password"; +"lng_connection_save" = "Save"; +"lng_settings_reset" = "Terminate other sessions"; +"lng_settings_reset_done" = "Other sessions terminated"; +"lng_settings_logout" = "Log Out"; +"lng_sure_logout" = "Are you sure you want to log out?"; + +"lng_settings_need_restart" = "You need to restart for applying\nsome of the new settings. Restart now?"; +"lng_settings_restart_now" = "Restart"; +"lng_settings_restart_later" = "Later"; + +"lng_profile_chat_unaccessible" = "Group is unaccessible"; +"lng_topbar_info" = "Info"; +"lng_profile_settings_section" = "Settings"; +"lng_profile_participants_section" = "Participants"; +"lng_profile_info" = "Contact info"; +"lng_profile_group_info" = "Group info"; +"lng_profile_add_contact" = "Add Contact"; +"lng_profile_edit_contact" = "Edit"; +"lng_profile_edit_group" = "Edit"; +"lng_profile_enable_notifications" = "Notifications"; +"lng_profile_clear_history" = "Clear history"; +"lng_profile_send_message" = "Send Message"; +"lng_profile_share_contact" = "Share Contact"; +"lng_profile_delete_contact" = "Delete"; +"lng_profile_set_group_photo" = "Set Photo"; +"lng_profile_add_participant" = "Add Member"; +"lng_profile_delete_and_exit" = "Leave"; +"lng_profile_kick" = "Kick"; +"lng_profile_sure_kick" = "Kick {user} from the group?"; +"lng_profile_loading" = "Loading.."; +"lng_profile_shared_media" = "Shared media"; +"lng_profile_no_media" = "No media in this conversation."; +"lng_profile_photos" = "{count:_not_used_|# photo|# photos} »"; +"lng_profile_photos_header" = "Photos overview"; +"lng_profile_videos" = "{count:_not_used_|# video file|# video files} »"; +"lng_profile_videos_header" = "Video files overview"; +"lng_profile_documents" = "{count:_not_used_|# document|# documents} »"; +"lng_profile_documents_header" = "Documents overview"; +"lng_profile_audios" = "{count:_not_used_|# voice message|# voice messages} »"; +"lng_profile_audios_header" = "Voice messages overview"; +"lng_profile_show_all_types" = "Show all types"; +"lng_profile_copy_phone" = "Copy phone number"; + +"lng_participant_filter" = "Search"; +"lng_participant_invite" = "Invite"; +"lng_create_new_group" = "New Group"; +"lng_create_group_next" = "Next"; +"lng_create_group_title" = "New Group"; + +"lng_sure_delete_contact" = "Are you sure, you want to delete {contact} from your contact list?"; +"lng_sure_delete_history" = "Are you sure, you want to delete all message history with {contact}?\n\nThis action cannot be undone."; + +"lng_sure_delete_and_exit" = "Are you sure, you want to delete all message history and leave «{group}»?\n\nThis action cannot be undone."; + +"lng_sure_enable_debug" = "Do you want to enable DEBUG mode?\n\nAll network events will be logged."; + +"lng_message_empty" = "(empty)"; + +"lng_action_add_user" = "{from} added {user}"; +"lng_action_kick_user" = "{from} kicked {user}"; +"lng_action_user_left" = "{from} left the group"; +"lng_action_user_joined" = "{from} joined the group"; +"lng_action_user_photo" = "{from} added a new profile photo"; +"lng_action_user_registered" = "{from} just joined Telegram"; +"lng_action_removed_photo" = "{from} removed group photo"; +"lng_action_changed_photo" = "{from} changed group photo"; +"lng_action_changed_title" = "{from} changed group name to «{title}»"; +"lng_action_created_chat" = "{from} created group «{title}»"; + +"lng_forwarded_from" = "Forwarded from "; + +"lng_attach_failed" = "Failed"; +"lng_attach_file" = "Document"; +"lng_attach_photo" = "Photo"; + +"lng_media_type" = "Media type"; +"lng_media_type_photos" = "Photos"; +"lng_media_type_videos" = "Video files"; +"lng_media_type_documents" = "Documents"; +"lng_media_type_audios" = "Voice messages"; + +"lng_media_open_with" = "Open With"; +"lng_media_download" = "Download"; +"lng_media_cancel" = "Cancel"; +"lng_media_video" = "Video file"; +"lng_media_audio" = "Voice message"; + +"lng_in_dlg_photo" = "Photo"; +"lng_in_dlg_video" = "Video"; +"lng_in_dlg_geo" = "Map"; +"lng_in_dlg_contact" = "Contact"; +"lng_in_dlg_audio" = "Audio"; +"lng_in_dlg_document" = "Document"; + +"lng_send_button" = "Send"; +"lng_message_ph" = "Write a message.."; +"lng_empty_history" = ""; +"lng_willbe_history" = "Please select chat to start messaging"; +"lng_message_with_from" = "[c]{from}:[/c] {message}"; +"lng_from_you" = "You"; + +"lng_typing" = "typing"; +"lng_user_typing" = "{user} is typing"; +"lng_users_typing" = "{user} and {second_user} are typing"; +"lng_many_typing" = "{count:_not_used_|# is|# are} typing"; +"lng_unread_bar" = "{count:_not_used_|# unread message|# unread messages}"; + +"lng_maps_point" = "Location"; +"lng_save_photo" = "Save image"; +"lng_save_video" = "Save video"; +"lng_save_audio" = "Save audio"; +"lng_save_document" = "Save document"; +"lng_save_downloaded" = "{ready} / {total} {mb}"; +"lng_duration_and_size" = "{duration}, {size}"; +"lng_choose_images" = "Choose images"; + +"lng_context_open_link" = "Open Link"; +"lng_context_copy_link" = "Copy Link"; +"lng_context_open_email" = "Write to this address"; +"lng_context_copy_email" = "Copy email address"; +"lng_context_open_hashtag" = "Search by hashtag"; +"lng_context_copy_hashtag" = "Copy hashtag"; +"lng_context_open_image" = "Open Image"; +"lng_context_save_image" = "Save Image As.."; +"lng_context_forward_image" = "Forward Image"; +"lng_context_delete_image" = "Delete Image"; +"lng_context_copy_image" = "Copy Image"; +"lng_context_close_image" = "Close Image"; +"lng_context_cancel_download" = "Cancel Download"; +"lng_context_show_in_folder" = "Show in Folder"; +"lng_context_show_in_finder" = "Show in Finder"; +"lng_context_open_video" = "Open Video"; +"lng_context_save_video" = "Save Video As.."; +"lng_context_open_audio" = "Open Audio"; +"lng_context_save_audio" = "Save Audio As.."; +"lng_context_open_document" = "Open File"; +"lng_context_save_document" = "Save File As.."; +"lng_context_forward_file" = "Forward File"; +"lng_context_delete_file" = "Delete File"; +"lng_context_close_file" = "Close File"; +"lng_context_copy_text" = "Copy Message Text"; +"lng_context_to_msg" = "Go To Message"; +"lng_context_forward_msg" = "Forward Message"; +"lng_context_delete_msg" = "Delete Message"; +"lng_context_select_msg" = "Select Message"; +"lng_context_cancel_upload" = "Cancel Upload"; +"lng_context_copy_selected" = "Copy Selected Text"; +"lng_context_forward_selected" = "Forward Selected"; +"lng_context_delete_selected" = "Delete Selected"; +"lng_context_clear_selection" = "Clear Selection"; +"lng_really_send_image" = "Do you want to send this image?"; +"lng_really_send_file" = "Do you want to send this file?"; +"lng_really_share_contact" = "Do you want to share this contact?"; +"lng_send_image_compressed" = "Send compressed image"; + +"lng_forward_choose" = "Choose recipient.."; +"lng_forward_confirm" = "Forward to {recipient}?"; +"lng_forward_share_contact" = "Share contact to {recipient}?"; +"lng_forward_send_file_confirm" = "Send «{name}» to {recipient}?"; +"lng_forward_send_files_confirm" = "Send selected files to {recipient}?"; +"lng_forward" = "Forward"; +"lng_forward_send" = "Send"; + +"lng_contact_phone" = "Phone number"; +"lng_enter_contact_data" = "New Contact"; +"lng_edit_group_title" = "Edit group name"; +"lng_edit_contact_title" = "Edit contact name"; +"lng_edit_self_title" = "Edit your name"; +"lng_confirm_contact_data" = "New Contact"; +"lng_add_contact" = "Create"; +"lng_add_contact_button" = "Add Contact"; +"lng_contacts_header" = "Contacts"; +"lng_contact_not_joined" = "Unfortunately {name} did not join Telegram yet, but you can send your friend an invitation.\n\nWe will notify you about any of your contacts who is joining Telegram."; +"lng_try_other_contact" = "Try other"; +"lng_contacts_done" = "Cancel"; + +"lng_drag_images_here" = "Drop images here"; +"lng_drag_photos_here" = "Drop photos here"; +"lng_drag_files_here" = "Drop files here"; + +"lng_drag_to_send_quick" = "to send them in a quick way"; +"lng_drag_to_send_no_compression" = "to send them without compression"; +"lng_drag_to_send_documents" = "to send them as documents"; + +"lng_selected_clear" = "Cancel"; +"lng_selected_delete" = "Delete"; +"lng_selected_forward" = "Forward"; +"lng_selected_count" = "{count:_not_used_|# message|# messages}"; +"lng_selected_cancel_sure_this" = "Do you want to cancel this upload?"; +"lng_selected_delete_sure_this" = "Do you want to delete this message?"; +"lng_selected_delete_sure" = "Do you want to delete {count:_not_used_|# message|# messages}?"; +"lng_selected_delete_confirm" = "Delete"; + +"lng_emoji_no_recent" = "Recent emojis will be here"; + +"lng_about_version" = "Version {version}"; +"lng_about_text" = "Official free messaging app based on [a href=\"https://core.telegram.org/mtproto\"]MTProto[/a] and\n[a href=\"https://core.telegram.org/api\"]Telegram API[/a] for speed and security\n\nThis software is licensed under [a href=\"https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\"]GNU GPL[/a] version 3,\nsource code is available on [a href=\"https://github.com/telegramdesktop/tdesktop\"]GitHub[/a]."; +"lng_about_done" = "Done"; + +"lng_search_found_results" = "{count:No messages found|Found # message|Found # messages}"; +"lng_search_global_results" = "Global search results"; + +"lng_mediaview_save" = "Download"; +"lng_mediaview_forward" = "Forward"; +"lng_mediaview_delete" = "Delete"; +"lng_mediaview_single_photo" = "Single Photo"; +"lng_mediaview_group_photo" = "Group Photo"; +"lng_mediaview_profile_photo" = "Profile Photo"; +"lng_mediaview_n_of_count" = "Photo {n} of {count}"; +"lng_mediaview_doc_image" = "Document"; + +"lng_mediaview_saved" = "Image was saved to your [c]Downloads[/c] folder"; + +"lng_new_authorization" = "{name},\nWe detected a login into your account from a new device on {day}, {date} at {time}\n\nDevice: {device}\nLocation: {location}\n\nIf this wasn't you, you can go to Settings — Terminate other sessions.\n\nThanks,\nThe Telegram Team"; + +// Mac specific + +"lng_mac_choose_app" = "Choose Application"; +"lng_mac_choose_text" = "Choose an application to open the document \"{file}\"."; +"lng_mac_enable_filter" = "Enable:"; +"lng_mac_recommended_apps" = "Recommended Applications"; +"lng_mac_all_apps" = "All Applications"; +"lng_mac_always_open_with" = "Always Open With"; +"lng_mac_this_app_can_open" = "This application can open \"{file}\"."; +"lng_mac_not_known_app" = "It's not known if this application can open \"{file}\"."; + +"lng_mac_menu_about" = "About Telegram"; +"lng_mac_menu_preferences" = "Preferences..."; +"lng_mac_menu_file" = "File"; +"lng_mac_menu_logout" = "Log Out"; +"lng_mac_menu_edit" = "Edit"; +"lng_mac_menu_undo" = "Undo"; +"lng_mac_menu_redo" = "Redo"; +"lng_mac_menu_cut" = "Cut"; +"lng_mac_menu_copy" = "Copy"; +"lng_mac_menu_paste" = "Paste"; +"lng_mac_menu_delete" = "Delete"; +"lng_mac_menu_select_all" = "Select All"; +"lng_mac_menu_window" = "Window"; +"lng_mac_menu_contacts" = "Contacts"; +"lng_mac_menu_add_contact" = "Add Contact"; +"lng_mac_menu_new_group" = "New Group"; +"lng_mac_menu_show" = "Show Telegram"; + +// Keys finished diff --git a/Telegram/Resources/lang.txt b/Telegram/Resources/lang.txt deleted file mode 100644 index 29fc0aeb31..0000000000 --- a/Telegram/Resources/lang.txt +++ /dev/null @@ -1,538 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014 John Preston, https://desktop.telegram.org -*/ -direction: "LTR"; - -lng_maintitle: "Telegram Desktop"; - -lng_menu_contacts: "Contacts"; -lng_menu_settings: "Settings"; -lng_menu_about: "About"; -lng_menu_update: "Update"; -lng_menu_restart: "Restart"; -lng_menu_back: "Back"; - -lng_open_from_tray: "Open Telegram"; -lng_minimize_to_tray: "Minimize to tray"; -lng_quit_from_tray: "Quit Telegram"; -lng_tray_icon_text: "Telegram is still running here, -you can change this from settings page. - -If this icon disappears from tray menu, -you can drag it back here from hidden icons."; - -lng_month1: "January"; -lng_month2: "February"; -lng_month3: "March"; -lng_month4: "April"; -lng_month5: "May"; -lng_month6: "June"; -lng_month7: "July"; -lng_month8: "August"; -lng_month9: "September"; -lng_month10: "October"; -lng_month11: "November"; -lng_month12: "December"; - -lng_weekday1: "Mon"; -lng_weekday2: "Tue"; -lng_weekday3: "Wed"; -lng_weekday4: "Thu"; -lng_weekday5: "Fri"; -lng_weekday6: "Sat"; -lng_weekday7: "Sun"; - -lng_weekday1_full: "Monday"; -lng_weekday2_full: "Tuesday"; -lng_weekday3_full: "Wednesday"; -lng_weekday4_full: "Thursday"; -lng_weekday5_full: "Friday"; -lng_weekday6_full: "Saturday"; -lng_weekday7_full: "Sunday"; - -lng_month_day: "{month} {day}"; - -lng_cancel: "Cancel"; -lng_continue: "Continue"; -lng_close: "Close"; -lng_connecting: "Connecting.."; -lng_reconnecting: "Reconnect in %1 s.."; -lng_reconnecting_try_now: "Try now"; - -lng_status_service_notifications: "service notifications"; -lng_status_offline: "last seen a long time ago"; -lng_status_recently: "last seen recently"; -lng_status_last_week: "last seen within a week"; -lng_status_last_month: "last seen within a month"; -lng_status_invisible: "invisible"; -lng_status_lastseen: "last seen {when}"; -lng_status_lastseen_now: "just now"; -lng_status_lastseen_minute: "%1 minute ago"; -lng_status_lastseen_minutes: "%1 minutes ago"; -lng_status_lastseen_hour: "%1 hour ago"; -lng_status_lastseen_hours: "%1 hours ago"; -lng_status_lastseen_today: "today at {time}"; -lng_status_lastseen_yesterday: "yesterday at {time}"; -lng_status_lastseen_date: "{date}"; -lng_status_lastseen_date_time: "{date} at {time}"; -lng_status_online: "online"; -lng_status_connecting: "connecting.."; - -lng_chat_no_members: "Group is unaccessible"; -lng_chat_members: "%1 members"; -lng_chat_members_online: "%1 members, %2 online"; - -lng_server_error: "Internal server error."; -lng_flood_error: "Too much tries. Please try again later."; -lng_deleted: "Unknown"; - -lng_intro: "Welcome to the official [a href=\"https://telegram.org/\"]Telegram[/a] desktop app. -It's [b]fast[/b] and [b]secure[/b]."; -lng_start_msgs: "START MESSAGING"; - -lng_intro_next: "NEXT"; -lng_intro_finish: "SIGN UP"; - -lng_phone_ph: "Your phone number"; -lng_phone_title: "Your Phone"; -lng_phone_desc: "Please confirm your country code and -enter your phone number."; -lng_phone_notreg: "Note: if you don't have a Telegram account yet, -please [b]sign up[/b] with your [a href=\"https://telegram.org/\"]iOS / Android[/a] or {signup}here »{/signup}"; -lng_country_code: "Country Code"; -lng_bad_country_code: "Invalid Country Code"; -lng_country_ph: "Search"; -lng_country_done: "Done"; -lng_country_none: "Country not found"; -lng_country_select: "Select Country"; - -lng_code_ph: "Your code"; -lng_code_desc: "We have sent you a message with activation -code to your phone. Please enter it below."; -lng_code_call: "Telegram will dial your number in %1:%2"; -lng_code_calling: "Requesting a call from Telegram.."; -lng_code_called: "Telegram dialed your number"; - -lng_bad_phone: "Invalid phone number. Please try again."; -lng_bad_phone_noreg: "Phone number not registered."; -lng_bad_code: "You have entered an invalid code. Please try again."; -lng_bad_name: "Please enter your first and last name."; -lng_bad_chat_title: "Please enter new chat title."; -lng_bad_photo: "Bad image selected."; - -lng_signup_title: "Information and photo"; -lng_signup_desc: "Please enter your name and -upload a photo."; -lng_signup_firstname: "First Name"; -lng_signup_lastname: "Last Name"; - -lng_dlg_filter: "Search"; -lng_dlg_conversations: "Conversations"; -lng_dlg_messages: "Messages"; -lng_dlg_new_group_name: "Group name"; -lng_dlg_create_group: "Create"; -lng_no_contacts: "You have no contacts"; -lng_contacts_loading: "Loading.."; -lng_contacts_not_found: "No contacts found"; - -lng_settings_profile: "Profile"; -lng_settings_edit: "Edit"; -lng_settings_save: "Save"; -lng_settings_cancel: "Cancel"; -lng_settings_upload: "Set Profile Photo"; -lng_settings_badsize: "This image has bad size, please try other."; -lng_settings_crop_profile: "Select square area for your profile photo"; -lng_settings_uploading_photo: "Uploading photo.."; - -lng_username_title: "Change username"; -lng_username_about: "You can choose a username on Telegram. -If you do, other people will be able to find -you by this username and contact you -without knowing your phone number. - -You can use a-z, 0-9 and underscores. -Minimum length is 5 characters."; -lng_username_invalid: "This name is invalid."; -lng_username_occupied: "This name is already occupied."; -lng_username_too_short: "This name is too short."; -lng_username_bad_symbols: "This name has bad symbols."; -lng_username_available: "This name is available."; -lng_username_not_found: "User @{user} not found."; - -lng_settings_section_contact_info: "Contact info"; -lng_settings_phone_number: "Phone number:"; -lng_settings_username: "Username:"; -lng_settings_choose_username: "choose username"; -lng_settings_change_username: "Change"; - -lng_settings_section_notify: "Notifications"; -lng_settings_desktop_notify: "Desktop notifications"; -lng_settings_show_name: "Show sender's name"; -lng_settings_show_preview: "Show message preview"; -lng_settings_sound_notify: "Play sound"; - -lng_notification_title: "Telegram Desktop"; -lng_notification_preview: "You have a new message"; - -lng_settings_section_general: "General"; -lng_settings_auto_update: "Update automatically"; -lng_settings_current_version: "Version {version}"; -lng_settings_check_now: "Check for updates"; -lng_settings_update_checking: "Checking for updates.."; -lng_settings_latest_installed: "Latest version is installed"; -lng_settings_downloading: "Downloading update {ready} / {total} Mb.."; -lng_settings_update_ready: "New version is ready"; -lng_settings_update_now: "Restart Now"; -lng_settings_update_fail: "Update check failed :("; -lng_settings_workmode_tray: "Show tray icon"; -lng_settings_workmode_window: "Show taskbar icon"; -lng_settings_auto_start: "Launch Telegram when system starts"; -lng_settings_start_min: "Launch minimized"; -lng_settings_add_sendto: "Place Telegram in «Send to» menu"; -lng_settings_scale_label: "Interface scale"; -lng_settings_scale_auto: "Auto ({cur})"; - -lng_settings_section_chat: "Chat options"; -lng_settings_replace_emojis: "Replace emojis"; -lng_settings_view_emojis: "View list"; -lng_settings_emoji_list: "List of supported emojis"; -lng_settings_send_enter: "Send by Enter"; -lng_settings_send_ctrlenter: "Send by Ctrl+Enter"; -lng_settings_send_cmdenter: "Send by Cmd+Enter"; -lng_settings_cats_and_dogs: "Allow cats and dogs"; - -lng_download_path_dont_ask: "Don't ask download path for each file"; -lng_download_path_label: "Download path: "; -lng_download_path_temp: "temp folder"; -lng_download_path_default: "default folder"; -lng_download_path_clear: "Clear All"; -lng_download_path_header: "Choose download path"; -lng_download_path_default_radio: "Telegram folder in system «Downloads»"; -lng_download_path_temp_radio: "Temp folder, cleared on logout or uninstall"; -lng_download_path_dir_radio: "Custom folder, cleared only manually"; -lng_download_path_choose: "Choose download path"; -lng_sure_clear_downloads: "Do you want to remove all downloaded files from temp folder? It is done automatically on logout or program uninstall."; -lng_download_path_failed: "File download could not be started. It could happen because of a bad download location. - -You can change download path in Settings."; -lng_download_path_settings: "Go to Settings"; -lng_download_finish_failed: "File download could not be finished. - -Would you like to try again?"; -lng_download_path_clearing: "Clearing.."; -lng_download_path_cleared: "Cleared!"; -lng_download_path_clear_failed: "Clear failed :("; - -lng_settings_section_cache: "Local storage"; -lng_settings_no_images_cached: "No cached images found!"; -lng_settings_image_cached: "Cached: {count} image, {size}"; -lng_settings_images_cached: "Cached: {count} images, {size}"; -lng_local_images_clear: "Clear All"; -lng_local_images_clearing: "Clearing.."; -lng_local_images_cleared: "Cleared!"; -lng_local_images_clear_failed: "Clear failed :("; - -lng_settings_section_advanced: "Advanced"; -lng_connection_type: "Connection type:"; -lng_connection_auto_connecting: "Default (connecting..)"; -lng_connection_auto: "Default ({type} used)"; -lng_connection_http_proxy: "HTTP with proxy"; -lng_connection_tcp_proxy: "TCP with proxy"; -lng_connection_header: "Connection type"; -lng_connection_auto_rb: "Auto (TCP if available or HTTP)"; -lng_connection_http_proxy_rb: "HTTP with custom http-proxy"; -lng_connection_tcp_proxy_rb: "TCP with custom socks5-proxy"; -lng_connection_host_ph: "Hostname"; -lng_connection_port_ph: "Port"; -lng_connection_user_ph: "Username"; -lng_connection_password_ph: "Password"; -lng_connection_save: "Save"; -lng_settings_reset: "Terminate other sessions"; -lng_settings_reset_done: "Other sessions terminated"; -lng_settings_logout: "Log Out"; -lng_sure_logout: "Are you sure you want to log out?"; - -lng_settings_need_restart: "You need to restart for applying -some of the new settings. Restart now?"; -lng_settings_restart_now: "Restart"; -lng_settings_restart_later: "Later"; - -lng_topbar_info: "Info"; -lng_profile_settings_section: "Settings"; -lng_profile_participants_section: "Participants"; -lng_profile_info: "Contact info"; -lng_profile_group_info: "Group info"; -lng_profile_add_contact: "Add Contact"; -lng_profile_edit_contact: "Edit"; -lng_profile_edit_group: "Edit"; -lng_profile_phone: "Phone number: {phone}"; -lng_profile_enable_notifications: "Notifications"; -lng_profile_clear_history: "Clear history"; -lng_profile_send_message: "Send Message"; -lng_profile_share_contact: "Share Contact"; -lng_profile_delete_contact: "Delete"; -lng_profile_set_group_photo: "Set Photo"; -lng_profile_add_participant: "Add Member"; -lng_profile_delete_and_exit: "Leave"; -lng_profile_kick: "Kick"; -lng_profile_sure_kick: "Kick {user} from the group?"; -lng_profile_loading: "Loading.."; -lng_profile_shared_media: "Shared media"; -lng_profile_no_media: "No media in this conversation."; -lng_profile_photo: "{count} photo »"; -lng_profile_photos: "{count} photos »"; -lng_profile_photos_header: "Photos overview"; -lng_profile_video: "{count} video file »"; -lng_profile_videos: "{count} video files »"; -lng_profile_videos_header: "Video files overview"; -lng_profile_document: "{count} document »"; -lng_profile_documents: "{count} documents »"; -lng_profile_documents_header: "Documents overview"; -lng_profile_audio: "{count} voice message »"; -lng_profile_audios: "{count} voice messages »"; -lng_profile_audios_header: "Voice messages overview"; -lng_profile_show_all_types: "Show all types"; -lng_profile_copy_phone: "Copy phone number"; - -lng_participant_filter: "Search"; -lng_participant_invite: "Invite"; -lng_create_new_group: "New Group"; -lng_create_group_next: "Next"; -lng_create_group_title: "New Group"; - -lng_sure_delete_contact: "Are you sure, you want to delete {contact} from your contact list?"; -lng_sure_delete_history: "Are you sure, you want to delete all message history with {contact}? - -This action cannot be undone."; - -lng_sure_delete_and_exit: "Are you sure, you want to delete all message history and leave «{group}»? - -This action cannot be undone."; - -lng_sure_enable_debug: "Do you want to enable DEBUG mode? - -All network events will be logged."; - -lng_message_empty: "(empty)"; - -lng_action_add_user: "{from} added {user}"; -lng_action_kick_user: "{from} kicked {user}"; -lng_action_user_left: "{from} left the group"; -lng_action_user_joined: "{from} joined the group"; -lng_action_user_photo: "{from} added a new profile photo"; -lng_action_user_registered: "{from} just joined Telegram"; -lng_action_removed_photo: "{from} removed group photo"; -lng_action_changed_photo: "{from} changed group photo"; -lng_action_changed_title: "{from} changed group name to «{title}»"; -lng_action_created_chat: "{from} created group «{title}»"; -lng_action_checked_in: "{from} checked in"; - -lng_forwarded_from: "Forwarded from "; - -lng_attach_failed: "Failed"; -lng_attach_file: "Document"; -lng_attach_photo: "Photo"; - -lng_media_type: "Media type"; -lng_media_type_photos: "Photos"; -lng_media_type_videos: "Video files"; -lng_media_type_documents: "Documents"; -lng_media_type_audios: "Voice messages"; - -lng_media_open_with: "Open With"; -lng_media_download: "Download"; -lng_media_cancel: "Cancel"; -lng_media_video: "Video file"; -lng_media_audio: "Voice message"; - -lng_in_dlg_photo: "Photo"; -lng_in_dlg_video: "Video"; -lng_in_dlg_geo: "Map"; -lng_in_dlg_contact: "Contact"; -lng_in_dlg_audio: "Audio"; -lng_in_dlg_document: "Document"; - -lng_send_button: "Send"; -lng_message_ph: "Write a message.."; -lng_empty_history: ""; -lng_willbe_history: "Please select chat to start messaging"; -lng_message_with_from: "[c]{from}:[/c] {message}"; -lng_from_you: "You"; - -lng_typing: "typing"; -lng_user_typing: "{user} is typing"; -lng_users_typing: "{user1} and {user2} are typing"; -lng_many_typing: "{n} are typing"; -lng_unread_bar: "%1 unread messages"; - -lng_maps_point: "Location"; -lng_save_photo: "Save image"; -lng_save_video: "Save video"; -lng_save_audio: "Save audio"; -lng_save_document: "Save document"; -lng_save_downloaded: "{ready} / {total} {mb}"; -lng_duration_and_size: "{duration}, {size}"; -lng_choose_images: "Choose images"; - -lng_context_open_link: "Open Link"; -lng_context_copy_link: "Copy Link"; -lng_context_open_email: "Write to this address"; -lng_context_copy_email: "Copy email address"; -lng_context_open_hashtag: "Search by hashtag"; -lng_context_copy_hashtag: "Copy hashtag"; -lng_context_open_image: "Open Image"; -lng_context_save_image: "Save Image As.."; -lng_context_forward_image: "Forward Image"; -lng_context_delete_image: "Delete Image"; -lng_context_copy_image: "Copy Image"; -lng_context_close_image: "Close Image"; -lng_context_cancel_download: "Cancel Download"; -lng_context_show_in_folder: "Show in Folder"; -lng_context_show_in_finder: "Show in Finder"; -lng_context_open_video: "Open Video"; -lng_context_save_video: "Save Video As.."; -lng_context_open_audio: "Open Audio"; -lng_context_save_audio: "Save Audio As.."; -lng_context_open_document: "Open File"; -lng_context_save_document: "Save File As.."; -lng_context_forward_file: "Forward File"; -lng_context_delete_file: "Delete File"; -lng_context_close_file: "Close File"; -lng_context_copy_text: "Copy Message Text"; -lng_context_to_msg: "Go To Message"; -lng_context_forward_msg: "Forward Message"; -lng_context_delete_msg: "Delete Message"; -lng_context_select_msg: "Select Message"; -lng_context_cancel_upload: "Cancel Upload"; -lng_context_copy_selected: "Copy Selected Text"; -lng_context_forward_selected: "Forward Selected"; -lng_context_delete_selected: "Delete Selected"; -lng_context_clear_selection: "Clear Selection"; -lng_really_send_image: "Do you want to send this image?"; -lng_really_send_file: "Do you want to send this file?"; -lng_really_share_contact: "Do you want to share this contact?"; -lng_send_image_compressed: "Send compressed image"; - -lng_forward_choose: "Choose recipient.."; -lng_forward_confirm: "Forward to {recipient}?"; -lng_forward_share_contact: "Share contact to {recipient}?"; -lng_forward_send_file_confirm: "Send «{name}» to {recipient}?"; -lng_forward_send_files_confirm: "Send selected files to {recipient}?"; -lng_forward: "Forward"; -lng_forward_send: "Send"; - -lng_contact_phone: "Phone number"; -lng_enter_contact_data: "New Contact"; -lng_edit_group_title: "Edit group name"; -lng_edit_contact_title: "Edit contact name"; -lng_edit_self_title: "Edit your name"; -lng_confirm_contact_data: "New Contact"; -lng_add_contact: "Create"; -lng_add_contact_button: "Add Contact"; -lng_contacts_header: "Contacts"; -lng_contact_not_joined: "Unfortunately {name} did not join Telegram yet, but you can send your friend an invitation. - -We will notify you about any of your contacts who is joining Telegram."; -lng_try_other_contact: "Try other"; -lng_contacts_done: "Cancel"; - -lng_drag_images_here: "Drop images here"; -lng_drag_photos_here: "Drop photos here"; -lng_drag_files_here: "Drop files here"; - -lng_drag_to_send_quick: "to send them in a quick way"; -lng_drag_to_send_no_compression: "to send them without compression"; -lng_drag_to_send_documents: "to send them as documents"; - -lng_selected_clear: "Cancel"; -lng_selected_delete: "Delete"; -lng_selected_forward: "Forward"; -lng_selected_count_1: "%1 message"; -lng_selected_count_5: "%1 messages"; -lng_selected_cancel_sure_this: "Do you want to cancel this upload?"; -lng_selected_delete_sure_this: "Do you want to delete this message?"; -lng_selected_delete_sure_1: "Do you want to delete %1 message?"; -lng_selected_delete_sure_5: "Do you want to delete %1 messages?"; -lng_selected_delete_confirm: "Delete"; - -lng_emoji_no_recent: "Recent emojis will be here"; - -lng_about_version: "Version {version}"; -lng_about_text: "Official free messaging app based on [a href=\"https://core.telegram.org/mtproto\"]MTProto[/a] and -[a href=\"https://core.telegram.org/api\"]Telegram API[/a] for speed and security - -This software is licensed under [a href=\"https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\"]GNU GPL[/a] version 3, -source code is available on [a href=\"https://github.com/telegramdesktop/tdesktop\"]GitHub[/a]."; -lng_about_done: "Done"; - -lng_search_no_results: "No messages found"; -lng_search_one_result: "Found {count} message"; -lng_search_n_results: "Found {count} messages"; -lng_search_global_results: "Global search results"; - -lng_mediaview_save: "Download"; -lng_mediaview_forward: "Forward"; -lng_mediaview_delete: "Delete"; -lng_mediaview_single_photo: "Single Photo"; -lng_mediaview_group_photo: "Group Photo"; -lng_mediaview_profile_photo: "Profile Photo"; -lng_mediaview_n_of_count: "Photo {n} of {count}"; -lng_mediaview_doc_image: "Document"; - -lng_mediaview_saved: "Image was saved to your [c]Downloads[/c] folder"; - -lng_new_authorization: "{name}, -We detected a login into your account from a new device on {day}, {date} at {time} - -Device: {device} -Location: {location} - -If this wasn't you, you can go to Settings — Terminate other sessions. - -Thanks, -The Telegram Team"; - -// Mac specific - -lng_mac_choose_app: "Choose Application"; -lng_mac_choose_text: "Choose an application to open the document \"{file}\"."; -lng_mac_enable_filter: "Enable:"; -lng_mac_recommended_apps: "Recommended Applications"; -lng_mac_all_apps: "All Applications"; -lng_mac_always_open_with: "Always Open With"; -lng_mac_this_app_can_open: "This application can open \"{file}\"."; -lng_mac_not_known_app: "It's not known if this application can open \"{file}\"."; - -lng_mac_menu_about: "About Telegram"; -lng_mac_menu_preferences: "Preferences..."; -lng_mac_menu_file: "File"; -lng_mac_menu_logout: "Log Out"; -lng_mac_menu_edit: "Edit"; -lng_mac_menu_undo: "Undo"; -lng_mac_menu_redo: "Redo"; -lng_mac_menu_cut: "Cut"; -lng_mac_menu_copy: "Copy"; -lng_mac_menu_paste: "Paste"; -lng_mac_menu_delete: "Delete"; -lng_mac_menu_select_all: "Select All"; -lng_mac_menu_window: "Window"; -lng_mac_menu_contacts: "Contacts"; -lng_mac_menu_add_contact: "Add Contact"; -lng_mac_menu_new_group: "New Group"; -lng_mac_menu_show: "Show Telegram"; - -// Keys finished diff --git a/Telegram/SourceFiles/_other/genlang.cpp b/Telegram/SourceFiles/_other/genlang.cpp index 4ce1f925f3..64323e39aa 100644 --- a/Telegram/SourceFiles/_other/genlang.cpp +++ b/Telegram/SourceFiles/_other/genlang.cpp @@ -39,10 +39,20 @@ Q_IMPORT_PLUGIN(QWebpPlugin) typedef unsigned int uint32; QString layoutDirection; -typedef QMap LangKeys; +typedef QMap LangKeys; LangKeys keys; -typedef QVector KeysOrder; +typedef QMap LangTags; +LangTags tags; +typedef QMap > LangKeysTags; +LangKeysTags keysTags; +typedef QVector KeysOrder; KeysOrder keysOrder; +KeysOrder tagsOrder; +typedef QMap > > LangKeysCounted; +LangKeysCounted keysCounted; + +static const QChar TextCommand(0x0010); +static const QChar TextCommandLangTag(0x0020); bool skipWhitespaces(const char *&from, const char *end) { while (from < end && (*from == ' ' || *from == '\n' || *from == '\t' || *from == '\r')) { @@ -86,68 +96,177 @@ bool skipJunk(const char *&from, const char *end) { return true; } +inline bool _lngEquals(const QByteArray &key, int from, int len, const char *value, int size) { + if (size != len || from + len > key.size()) return false; + for (const char *v = key.constData() + from, *e = v + len; v != e; ++v, ++value) { + if (*v != *value) return false; + } + return true; +} + +#define LNG_EQUALS_PART(key, from, len, value) _lngEquals(key, from, len, value, sizeof(value) - 1) +#define LNG_EQUALS_TAIL(key, from, value) _lngEquals(key, from, key.size() - from, value, sizeof(value) - 1) +#define LNG_EQUALS(key, value) _lngEquals(key, 0, key.size(), value, sizeof(value) - 1) + +static const int MaxCountedValues = 6; + void readKeyValue(const char *&from, const char *end) { if (!skipJunk(from, end)) return; - const char *nameStart = from; + if (*from != '"') throw Exception(QString("Expected quote before key name!")); + const char *nameStart = ++from; while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) { ++from; } - QString varName = QString::fromUtf8(nameStart, int(from - nameStart)); + if (from == nameStart) throw Exception(QString("Expected key name!")); + QByteArray varName = QByteArray(nameStart, int(from - nameStart)); + for (const char *t = nameStart; t + 1 < from; ++t) { + if (*t == '_') { + if (*(t + 1) == '_') throw Exception(QString("Bad key name: %1").arg(QLatin1String(varName))); + ++t; + } + } - if (!skipJunk(from, end)) throw Exception("Unexpected end of file!"); - if (*from != ':') throw Exception(QString("':' expected after '%1'").arg(varName)); + if (from == end || *from != '"') throw Exception(QString("Expected quote after key name in key '%1'!").arg(QLatin1String(varName))); + ++from; - if (!skipJunk(++from, end)) throw Exception("Unexpected end of file!"); - if (*from != '"') throw Exception(QString("Expected string after '%1:'").arg(varName)); + if (!skipJunk(from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName))); + if (*from != '=') throw Exception(QString("'=' expected in key '%1'!").arg(QLatin1String(varName))); + + if (!skipJunk(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName))); + if (*from != '"') throw Exception(QString("Expected string after '=' in key '%1'!").arg(QLatin1String(varName))); QByteArray varValue; const char *start = ++from; + QVector tagsList; while (from < end && *from != '"') { + if (*from == '\n') { + throw Exception(QString("Unexpected end of string in key '%1'!").arg(QLatin1String(varName))); + } if (*from == '\\') { - if (from + 1 >= end) throw Exception("Unexpected end of file!"); - if (*(from + 1) == '"' || *(from + 1) == '\\') { + if (from + 1 >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName))); + if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{') { if (from > start) varValue.append(start, int(from - start)); start = ++from; + } else if (*(from + 1) == 'n') { + if (from > start) varValue.append(start, int(from - start)); + + varValue.append('\n'); + + start = (++from) + 1; } + } else if (*from == '{') { + if (from > start) varValue.append(start, int(from - start)); + + const char *tagStart = ++from; + while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) { + ++from; + } + if (from == tagStart) throw Exception(QString("Expected tag name in key '%1'!").arg(QLatin1String(varName))); + QByteArray tagName = QByteArray(tagStart, int(from - tagStart)); + + if (from == end || (*from != '}' && *from != ':')) throw Exception(QString("Expected '}' or ':' after tag name in key '%1'!").arg(QLatin1String(varName))); + + LangTags::const_iterator i = tags.constFind(tagName); + if (i == tags.cend()) { + i = tags.insert(tagName, tagsOrder.size()); + tagsOrder.push_back(tagName); + } + if (0x0020 + *i > 0x00F7) throw Exception(QString("Too many different tags in key '%1'").arg(QLatin1String(varName))); + + QString tagReplacer(4, TextCommand); + tagReplacer[1] = TextCommandLangTag; + tagReplacer[2] = QChar(0x0020 + *i); + varValue.append(tagReplacer.toUtf8()); + for (int j = 0, s = tagsList.size(); j < s; ++j) { + if (tagsList.at(j) == tagName) throw Exception(QString("Tag '%1' double used in key '%2'!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + } + tagsList.push_back(tagName); + + if (*from == ':') { + start = ++from; + + QVector &counted(keysCounted[varName][tagName]); + QByteArray subvarValue; + bool foundtag = false; + while (from < end && *from != '"' && *from != '}') { + if (*from == '|') { + if (from > start) subvarValue.append(start, int(from - start)); + counted.push_back(QString::fromUtf8(subvarValue)); + subvarValue = QByteArray(); + foundtag = false; + start = from + 1; + } + if (*from == '\n') { + throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + } + if (*from == '\\') { + if (from + 1 >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{' || *(from + 1) == '#') { + if (from > start) subvarValue.append(start, int(from - start)); + start = ++from; + } else if (*(from + 1) == 'n') { + if (from > start) subvarValue.append(start, int(from - start)); + + subvarValue.append('\n'); + + start = (++from) + 1; + } + } else if (*from == '{') { + throw Exception(QString("Unexpected tag inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + } else if (*from == '#') { + if (foundtag) throw Exception(QString("Replacement '#' double used inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + foundtag = true; + if (from > start) subvarValue.append(start, int(from - start)); + subvarValue.append(tagReplacer.toUtf8()); + start = from + 1; + } + ++from; + } + if (from >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + if (*from == '"') throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + + if (from > start) subvarValue.append(start, int(from - start)); + counted.push_back(QString::fromUtf8(subvarValue)); + + if (counted.size() > MaxCountedValues) { + throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + } + } + start = from + 1; } ++from; } - if (from >= end) throw Exception("Unexpected end of file!"); + if (from >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName))); if (from > start) varValue.append(start, int(from - start)); - if (!skipJunk(++from, end)) throw Exception("Unexpected end of file!"); - if (*from != ';') throw Exception(QString("';' expected after '%1: \"value\"'").arg(varName)); + if (!skipJunk(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName))); + if (*from != ';') throw Exception(QString("';' expected after \"value\" in key '%1'!").arg(QLatin1String(varName))); skipJunk(++from, end); if (varName == "direction") { - if (varValue == "LTR" || varValue == "RTL") { - layoutDirection = QString::fromUtf8(varValue); - } else { - throw Exception(QString("Unexpected value for 'direction' key: '%1'").arg(QString::fromUtf8(varValue))); - } - } else if (varName.midRef(0, 4) != "lng_") { - throw Exception(QString("Bad key '%1'").arg(varName)); + throw Exception(QString("Unexpected value for 'direction' in key '%1'!").arg(QLatin1String(varName))); + } else if (!LNG_EQUALS_PART(varName, 0, 4, "lng_")) { + throw Exception(QString("Bad key '%1'!").arg(QLatin1String(varName))); } else if (keys.constFind(varName) != keys.cend()) { - throw Exception(QString("Key doubled '%1'").arg(varName)); + throw Exception(QString("Key '%1' doubled!").arg(QLatin1String(varName))); } else { keys.insert(varName, QString::fromUtf8(varValue)); + keysTags.insert(varName, tagsList); keysOrder.push_back(varName); } } -QString escapeCpp(const QString &key, QString value, bool wideChar) { +QString escapeCpp(const QByteArray &key, QString value, bool wideChar) { if (value.isEmpty()) return "QString()"; - value = value.replace('\\', "\\\\").replace('\n', "\\n").replace('\r', "").replace('"', "\\\""); + QString res; res.reserve(value.size() * 10); bool instr = false; for (const QChar *ch = value.constData(), *e = value.constData() + value.size(); ch != e; ++ch) { - if (ch->unicode() < 32) { - throw Exception(QString("Bad value for key '%1'").arg(key)); - } else if (ch->unicode() > 127) { + if (ch->unicode() > 0x00F7) { if (instr) { res.append('"'); instr = false; @@ -170,14 +289,39 @@ QString escapeCpp(const QString &key, QString value, bool wideChar) { res.append('"'); instr = true; } - res.append(*ch); + if (ch->unicode() == '\\' || ch->unicode() == '\n' || ch->unicode() == '\r' || ch->unicode() == '"') { + res.append('\\'); + if (ch->unicode() == '\\' || ch->unicode() == '"') { + res.append(*ch); + } else if (ch->unicode() == '\n') { + res.append('n'); + } else if (ch->unicode() == '\r') { + res.append('r'); + } + } else if (ch->unicode() < 0x0020) { + if (*ch == TextCommand) { + if (ch + 3 >= e || (ch + 1)->unicode() != TextCommandLangTag || (ch + 2)->unicode() > 0x00F7 || (ch + 2)->unicode() < 0x0020 || *(ch + 3) != TextCommand) { + throw Exception(QString("Bad value for key '%1'").arg(QLatin1String(key))); + } else { + res.append('\\').append('x').append(QString("%1").arg(ch->unicode(), 2, 16, QChar('0'))); + res.append('\\').append('x').append(QString("%1").arg((ch + 1)->unicode(), 2, 16, QChar('0'))); + res.append('\\').append('x').append(QString("%1").arg((ch + 2)->unicode(), 2, 16, QChar('0'))); + res.append('\\').append('x').append(QString("%1").arg((ch + 3)->unicode(), 2, 16, QChar('0'))); + ch += 3; + } + } else { + throw Exception(QString("Bad value for key '%1'").arg(QLatin1String(key))); + } + } else { + res.append(*ch); + } } } if (instr) res.append('"'); return (wideChar ? "qsl(" : "QString::fromUtf8(") + res.mid(wideChar ? 2 : 1) + ")"; } -void writeCppKey(QTextStream &tcpp, const QString &key, const QString &val) { +void writeCppKey(QTextStream &tcpp, const QByteArray &key, const QString &val) { QString wide = escapeCpp(key, val, true), utf = escapeCpp(key, val, false); if (wide.indexOf(" L\"") < 0) { tcpp << "\t\t\tset(" << key << ", " << wide << ");\n"; @@ -236,49 +380,62 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ */\n"; th << "#pragma once\n\n"; + + for (int i = 0, l = tagsOrder.size(); i < l; ++i) { + th << "enum lngtag_" << tagsOrder[i] << " { lt_" << tagsOrder[i] << " = " << i << " };\n"; + } + th << "static const ushort lngtags_cnt = " << tagsOrder.size() << ";\n"; + th << "static const ushort lngtags_max_counted_values = " << MaxCountedValues << ";\n"; + th << "\n"; + th << "enum LangKey {\n"; for (int i = 0, l = keysOrder.size(); i < l; ++i) { - th << "\t" << keysOrder[i] << (i ? "" : " = 0") << ",\n"; + if (keysTags[keysOrder[i]].isEmpty()) { + th << "\t" << keysOrder[i] << (i ? "" : " = 0") << ",\n"; + } else { + th << "\t" << keysOrder[i] << "__tagged" << (i ? "" : " = 0") << ",\n"; + QMap > &countedTags(keysCounted[keysOrder[i]]); + if (!countedTags.isEmpty()) { + for (QMap >::const_iterator j = countedTags.cbegin(), e = countedTags.cend(); j != e; ++j) { + const QVector &counted(*j); + for (int k = 0, s = counted.size(); k < s; ++k) { + th << "\t" << keysOrder[i] << "__" + j.key() + QString::number(k).toUtf8() << ",\n"; + } + } + } + } } - th << "\n\tlng_keys_cnt\n"; + th << "\n\tlngkeys_cnt\n"; th << "};\n\n"; - th << "QString lang(LangKey key);\n"; - th << "inline QString langDayOfMonth(const QDate &date) {\n"; - th << "\tint32 month = date.month(), day = date.day();\n"; - th << "\treturn (month > 0 && month <= 12) ? lang(lng_month_day).replace(qsl(\"{month}\"), lang(LangKey(lng_month1 + month - 1))).replace(qsl(\"{day}\"), QString::number(day)) : qsl(\"{err}\");\n"; - th << "}\n\n"; - th << "inline QString langDayOfWeek(const QDate &date) {\n"; - th << "\tint32 day = date.dayOfWeek();\n"; - th << "\treturn (day > 0 && day <= 7) ? lang(LangKey(lng_weekday1 + day - 1)) : qsl(\"{err}\");\n"; - th << "}\n\n"; - th << "inline QString langDayOfWeekFull(const QDate &date) {\n"; - th << "\tint32 day = date.dayOfWeek();\n"; - th << "\treturn (day > 0 && day <= 7) ? lang(LangKey(lng_weekday1_full + day - 1)) : qsl(\"{err}\");\n"; - th << "}\n\n"; - th << "Qt::LayoutDirection langDir();\n\n"; - th << "class LangLoader {\n"; - th << "public:\n"; - th << "\tconst QString &errors() const;\n"; - th << "\tconst QString &warnings() const;\n\n"; - th << "protected:\n"; - th << "\tLangLoader() : _checked(false) {\n"; - th << "\t\tmemset(_found, 0, sizeof(_found));\n"; - th << "\t}\n\n"; - th << "\tbool feedKeyValue(const QString &key, const QString &value);\n\n"; - th << "\tvoid error(const QString &text) {\n"; - th << "\t\t_err.push_back(text);\n"; - th << "\t}\n"; - th << "\tvoid warning(const QString &text) {\n"; - th << "\t\t_warn.push_back(text);\n"; - th << "\t}\n\n"; - th << "private:\n"; - th << "\tmutable QStringList _err, _warn;\n"; - th << "\tmutable QString _errors, _warnings;\n"; - th << "\tmutable bool _checked;\n"; - th << "\tbool _found[lng_keys_cnt];\n\n"; - th << "\tLangLoader(const LangLoader &);\n"; - th << "\tLangLoader &operator=(const LangLoader &);\n"; - th << "};\n"; + + th << "LangString lang(LangKey key);\n\n"; + + for (int i = 0, l = keysOrder.size(); i < l; ++i) { + QVector &tagsList(keysTags[keysOrder[i]]); + if (tagsList.isEmpty()) continue; + + QMap > &countedTags(keysCounted[keysOrder[i]]); + th << "inline LangString " << keysOrder[i] << "("; + for (int j = 0, s = tagsList.size(); j < s; ++j) { + if (countedTags[tagsList[j]].isEmpty()) { + th << "lngtag_" << tagsList[j] << ", const QString &" << tagsList[j] << "__val"; + } else { + th << "lngtag_" << tagsList[j] << ", float64 " << tagsList[j] << "__val"; + } + if (j + 1 < s) th << ", "; + } + th << ") {\n"; + th << "\treturn lang(" << keysOrder[i] << "__tagged)"; + for (int j = 0, s = tagsList.size(); j < s; ++j) { + if (countedTags[tagsList[j]].isEmpty()) { + th << ".tag(lt_" << tagsList[j] << ", " << tagsList[j] << "__val)"; + } else { + th << ".tag(lt_" << tagsList[j] << ", langCounted(" << keysOrder[i] << "__" << tagsList[j] << "0, lt_" << tagsList[j] << ", " << tagsList[j] << "__val))"; + } + } + th << ";\n"; + th << "}\n"; + } tcpp << "\ /*\n\ @@ -304,64 +461,127 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ */\n"; tcpp << "#include \"stdafx.h\"\n#include \"lang.h\"\n\n"; tcpp << "namespace {\n"; - tcpp << "\tQt::LayoutDirection _langDir = Qt::" << (layoutDirection == "LTR" ? "LeftToRight" : "RightToLeft") << ";\n"; - tcpp << "\tconst char *_langKeyNames[lng_keys_cnt + 1] = {\n"; + + tcpp << "\tconst char *_langKeyNames[lngkeys_cnt] = {\n"; for (int i = 0, l = keysOrder.size(); i < l; ++i) { tcpp << "\t\t\"" << keysOrder[i] << "\",\n"; } - tcpp << "\t\t\"lng_keys_cnt\"\n"; tcpp << "\t};\n\n"; - tcpp << "\tQString _langValues[lng_keys_cnt + 1];\n\n"; + + tcpp << "\tLangString _langValues[lngkeys_cnt];\n\n"; tcpp << "\tvoid set(LangKey key, const QString &val) {\n"; tcpp << "\t\t_langValues[key] = val;\n"; tcpp << "\t}\n\n"; + tcpp << "\tclass LangInit {\n"; tcpp << "\tpublic:\n"; tcpp << "\t\tLangInit() {\n"; for (int i = 0, l = keysOrder.size(); i < l; ++i) { - writeCppKey(tcpp, keysOrder[i], keys[keysOrder[i]]); + writeCppKey(tcpp, keysOrder[i] + (keysTags[keysOrder[i]].isEmpty() ? "" : "__tagged"), keys[keysOrder[i]]); + + QMap > &countedTags(keysCounted[keysOrder[i]]); + if (!countedTags.isEmpty()) { + for (QMap >::const_iterator j = countedTags.cbegin(), e = countedTags.cend(); j != e; ++j) { + const QVector &counted(*j); + for (int k = 0, s = counted.size(); k < s; ++k) { + writeCppKey(tcpp, keysOrder[i] + "__" + j.key() + QString::number(k).toUtf8(), counted[k]); + } + } + } } - tcpp << "\t\t\tset(lng_keys_cnt, QString());\n"; tcpp << "\t\t}\n"; tcpp << "\t};\n\n"; - tcpp << "\tLangInit _langInit;\n"; - tcpp << "}\n\n"; - tcpp << "QString lang(LangKey key) {\n"; - tcpp << "\treturn _langValues[(key < 0 || key > lng_keys_cnt) ? lng_keys_cnt : key];\n"; - tcpp << "}\n\n"; + tcpp << "\tLangInit _langInit;\n\n"; - tcpp << "Qt::LayoutDirection langDir() {\n"; - tcpp << "\treturn _langDir;\n"; - tcpp << "}\n\n"; - - tcpp << "bool LangLoader::feedKeyValue(const QString &key, const QString &value) {\n"; - tcpp << "\tif (key == qsl(\"direction\")) {\n"; - tcpp << "\t\tif (value == qsl(\"LTR\")) {\n"; - tcpp << "\t\t\t_langDir = Qt::LeftToRight;\n"; - tcpp << "\t\t\treturn true;\n"; - tcpp << "\t\t} else if (value == qsl(\"RTL\")) {\n"; - tcpp << "\t\t\t_langDir = Qt::RightToLeft;\n"; - tcpp << "\t\t\treturn true;\n"; - tcpp << "\t\t} else {\n"; - tcpp << "\t\t\t_err.push_back(qsl(\"Bad value for 'direction' key.\"));\n"; - tcpp << "\t\t\treturn false;\n"; + tcpp << "\tinline bool _lngEquals(const QByteArray &key, int from, int len, const char *value, int size) {\n"; + tcpp << "\t\tif (size != len || from + len > key.size()) return false;\n"; + tcpp << "\t\tfor (const char *v = key.constData() + from, *e = v + len; v != e; ++v, ++value) {\n"; + tcpp << "\t\t\tif (*v != *value) return false;\n"; tcpp << "\t\t}\n"; + tcpp << "\t\treturn true; \n"; tcpp << "\t}\n"; - tcpp << "\tif (key.size() < 5 || key.midRef(0, 4) != qsl(\"lng_\")) {\n"; - tcpp << "\t\t_err.push_back(qsl(\"Bad key name '%1'\").arg(key));\n"; - tcpp << "\t\treturn false;\n"; - tcpp << "\t}\n\n"; + + tcpp << "}\n\n"; + + tcpp << "#define LNG_EQUALS_PART(key, from, len, value) _lngEquals(key, from, len, value, sizeof(value) - 1)\n"; + tcpp << "#define LNG_EQUALS_TAIL(key, from, value) _lngEquals(key, from, key.size() - from, value, sizeof(value) - 1)\n"; + tcpp << "#define LNG_EQUALS(key, value) _lngEquals(key, 0, key.size(), value, sizeof(value) - 1)\n\n"; + + tcpp << "LangString lang(LangKey key) {\n"; + tcpp << "\treturn (key < 0 || key > lngkeys_cnt) ? QString() : _langValues[key];\n"; + tcpp << "}\n\n"; + + tcpp << "const char *langKeyName(LangKey key) {\n"; + tcpp << "\treturn (key < 0 || key > lngkeys_cnt) ? \"\" : _langKeyNames[key];\n"; + tcpp << "}\n\n"; + + tcpp << "ushort LangLoader::tagIndex(const QByteArray &tag) const {\n"; + tcpp << "\tif (tag.isEmpty()) return lngtags_cnt;\n\n"; + if (!tags.isEmpty()) { + QString tab("\t"); + tcpp << "\tconst char *ch = tag.constData(), *e = tag.constData() + tag.size();\n"; + QByteArray current; + int depth = current.size(); + tcpp << "\tswitch (*ch) {\n"; + for (LangTags::const_iterator i = tags.cbegin(), j = i + 1, e = tags.cend(); i != e; ++i) { + QByteArray tag = i.key(); + while (depth > 0 && tag.mid(0, depth) != current) { + tcpp << tab.repeated(depth + 1) << "}\n"; + current.chop(1); + --depth; + tcpp << tab.repeated(depth + 1) << "break;\n"; + } + do { + if (tag == current) break; + + char ich = i.key().at(current.size()); + tcpp << tab.repeated(current.size() + 1) << "case '" << ich << "':\n"; + if (j == e || ich != ((j.key().size() > depth) ? j.key().at(depth) : 0)) { + if (tag == current + ich) { + tcpp << tab.repeated(depth + 1) << "\tif (ch + " << (depth + 1) << " == e) return lt_" << tag << ";\n"; + } else { + tcpp << tab.repeated(depth + 1) << "\tif (LNG_EQUALS_TAIL(tag, " << (depth + 1) << ", \"" << i.key().mid(depth + 1) << "\")) return lt_" << tag << ";\n"; + } + tcpp << tab.repeated(depth + 1) << "break;\n"; + break; + } + + ++depth; + current += ich; + + if (tag == current) { + tcpp << tab.repeated(depth + 1) << "if (ch + " << depth << " == e) {\n"; + tcpp << tab.repeated(depth + 1) << "\treturn lt_" << tag << ";\n"; + tcpp << tab.repeated(depth + 1) << "}\n"; + } + + tcpp << tab.repeated(depth + 1) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n"; + } while (true); + ++j; + } + while (QByteArray() != current) { + tcpp << tab.repeated(depth + 1) << "}\n"; + current.chop(1); + --depth; + tcpp << tab.repeated(depth + 1) << "break;\n"; + } + tcpp << "\t}\n\n"; + } + tcpp << "\treturn lngtags_cnt;\n"; + tcpp << "}\n\n"; + + tcpp << "LangKey LangLoader::keyIndex(const QByteArray &key) const {\n"; + tcpp << "\tif (key.size() < 5 || !LNG_EQUALS_PART(key, 0, 4, \"lng_\")) return lngkeys_cnt;\n\n"; if (!keys.isEmpty()) { QString tab("\t"); - tcpp << "\tLangKey keyIndex = lng_keys_cnt;\n"; - tcpp << "\tconst QChar *ch = key.constData(), *e = key.constData() + key.size();\n"; - QString current("lng_"); + tcpp << "\tconst char *ch = key.constData(), *e = key.constData() + key.size();\n"; + QByteArray current("lng_"); int depth = current.size(); - tcpp << "\tswitch ((ch + " << depth << ")->unicode()) {\n"; + tcpp << "\tswitch (*(ch + " << depth << ")) {\n"; for (LangKeys::const_iterator i = keys.cbegin(), j = i + 1, e = keys.cend(); i != e; ++i) { - QString key = i.key(); - while (key.midRef(0, depth) != current) { + QByteArray key = i.key(); + while (key.mid(0, depth) != current) { tcpp << tab.repeated(depth - 3) << "}\n"; current.chop(1); --depth; @@ -370,13 +590,13 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ do { if (key == current) break; - QChar ich = i.key().at(current.size()); + char ich = i.key().at(current.size()); tcpp << tab.repeated(current.size() - 3) << "case '" << ich << "':\n"; if (j == e || ich != ((j.key().size() > depth) ? j.key().at(depth) : 0)) { if (key == current + ich) { - tcpp << tab.repeated(depth - 3) << "\tif (ch + " << (depth + 1) << " == e) keyIndex = " << key << ";\n"; + tcpp << tab.repeated(depth - 3) << "\tif (ch + " << (depth + 1) << " == e) return " << key << (keysTags[key].isEmpty() ? "" : "__tagged") << ";\n"; } else { - tcpp << tab.repeated(depth - 3) << "\tif (key.midRef(" << (depth + 1) << ") == qsl(\"" << i.key().mid(depth + 1) << "\")) keyIndex = " << key << ";\n"; + tcpp << tab.repeated(depth - 3) << "\tif (LNG_EQUALS_TAIL(key, " << (depth + 1) << ", \"" << i.key().mid(depth + 1) << "\")) return " << key << (keysTags[key].isEmpty() ? "" : "__tagged") << ";\n"; } tcpp << tab.repeated(depth - 3) << "break;\n"; break; @@ -387,52 +607,78 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ if (key == current) { tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " == e) {\n"; - tcpp << tab.repeated(depth - 3) << "\tkeyIndex = " << key << ";\n"; + tcpp << tab.repeated(depth - 3) << "\treturn " << key << (keysTags[key].isEmpty() ? "" : "__tagged") << ";\n"; tcpp << tab.repeated(depth - 3) << "}\n"; } - tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " < e) switch ((ch + " << depth << ")->unicode()) {\n"; + tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n"; } while (true); ++j; } - while (QString("lng_") != current) { + while (QByteArray("lng_") != current) { tcpp << tab.repeated(depth - 3) << "}\n"; current.chop(1); --depth; tcpp << tab.repeated(depth - 3) << "break;\n"; } tcpp << "\t}\n\n"; - tcpp << "\tif (keyIndex < lng_keys_cnt) {\n"; - tcpp << "\t\t_found[keyIndex] = 1;\n"; - tcpp << "\t\t_langValues[keyIndex] = value;\n"; - tcpp << "\t\treturn true;\n"; + } + tcpp << "\treturn lngkeys_cnt;\n"; + tcpp << "}\n\n"; + + tcpp << "bool LangLoader::tagReplaced(LangKey key, ushort tag) const {\n"; + if (!tags.isEmpty()) { + tcpp << "\tswitch (key) {\n"; + for (int i = 0, l = keysOrder.size(); i < l; ++i) { + QVector &tagsList(keysTags[keysOrder[i]]); + if (tagsList.isEmpty()) continue; + + tcpp << "\tcase " << keysOrder[i] << "__tagged: {\n"; + tcpp << "\t\tswitch (tag) {\n"; + for (int j = 0, s = tagsList.size(); j < s; ++j) { + tcpp << "\t\tcase lt_" << tagsList[j] << ":\n"; + } + tcpp << "\t\t\treturn true;\n"; + tcpp << "\t\t}\n"; + tcpp << "\t} break;\n"; + } tcpp << "\t}\n\n"; } - tcpp << "\t_err.push_back(qsl(\"Unknown key name '%1'\").arg(key));\n"; + tcpp << "\treturn false;"; + tcpp << "}\n\n"; + + tcpp << "LangKey LangLoader::subkeyIndex(LangKey key, ushort tag, ushort index) const {\n"; + tcpp << "\tif (index >= lngtags_max_counted_values) return lngkeys_cnt;\n\n"; + if (!tags.isEmpty()) { + tcpp << "\tswitch (key) {\n"; + for (int i = 0, l = keysOrder.size(); i < l; ++i) { + QVector &tagsList(keysTags[keysOrder[i]]); + if (tagsList.isEmpty()) continue; + + QMap > &countedTags(keysCounted[keysOrder[i]]); + tcpp << "\tcase " << keysOrder[i] << "__tagged: {\n"; + tcpp << "\t\tswitch (tag) {\n"; + for (int j = 0, s = tagsList.size(); j < s; ++j) { + if (!countedTags[tagsList[j]].isEmpty()) { + tcpp << "\t\tcase lt_" << tagsList[j] << ": return LangKey(" << keysOrder[i] << "__" << tagsList[j] << "0 + index);\n"; + } + } + tcpp << "\t\t}\n"; + tcpp << "\t} break;\n"; + } + tcpp << "\t}\n\n"; + } + tcpp << "\treturn lngkeys_cnt;"; + tcpp << "}\n\n"; + + tcpp << "bool LangLoader::feedKeyValue(LangKey key, const QString &value) {\n"; + tcpp << "\tif (key < lngkeys_cnt) {\n"; + tcpp << "\t\t_found[key] = 1;\n"; + tcpp << "\t\t_langValues[key] = value;\n"; + tcpp << "\t\treturn true;\n"; + tcpp << "\t}\n"; tcpp << "\treturn false;\n"; tcpp << "}\n\n"; - - tcpp << "const QString &LangLoader::errors() const {\n"; - tcpp << "\tif (_errors.isEmpty() && !_err.isEmpty()) {\n"; - tcpp << "\t\t_errors = _err.join('\\n');\n"; - tcpp << "\t}\n"; - tcpp << "\treturn _errors;\n"; - tcpp << "}\n\n"; - - tcpp << "const QString &LangLoader::warnings() const {\n"; - tcpp << "\tif (!_checked) {\n"; - tcpp << "\t\tfor (int32 i = 0; i < lng_keys_cnt; ++i) {\n"; - tcpp << "\t\t\tif (!_found[i]) {\n"; - tcpp << "\t\t\t\t_warn.push_back(qsl(\"No value found for key '%1'\").arg(_langKeyNames[i]));\n"; - tcpp << "\t\t\t}\n"; - tcpp << "\t\t}\n"; - tcpp << "\t\t_checked = true;\n"; - tcpp << "\t}\n"; - tcpp << "\tif (_warnings.isEmpty() && !_warn.isEmpty()) {\n"; - tcpp << "\t\t_warnings = _warn.join('\\n');\n"; - tcpp << "\t}\n"; - tcpp << "\treturn _warnings;\n"; - tcpp << "}\n"; } QFile cpp(lang_cpp), h(lang_h); diff --git a/Telegram/SourceFiles/_other/mlmain.cpp b/Telegram/SourceFiles/_other/mlmain.cpp index 51bed11dd1..f37e75a196 100644 --- a/Telegram/SourceFiles/_other/mlmain.cpp +++ b/Telegram/SourceFiles/_other/mlmain.cpp @@ -18,7 +18,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "mlmain.h" int main(int argc, char *argv[]) { - QString lang_in("lang.txt"), lang_out("lang"); + QString lang_in("lang.strings"), lang_out("lang"); for (int i = 0; i < argc; ++i) { if (string("-lang_in") == argv[i]) { if (++i < argc) lang_in = argv[i]; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index d079e14211..9c12cc531a 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -230,39 +230,29 @@ namespace App { if (precise) { QDateTime dOnline(date(online)), dNow(date(now)); if (dOnline.date() == dNow.date()) { - when = lang(lng_status_lastseen_today).replace(qsl("{time}"), dOnline.time().toString(qsl("hh:mm"))); + return lng_status_lastseen_today(lt_time, dOnline.time().toString(qsl("hh:mm"))); } else if (dOnline.date().addDays(1) == dNow.date()) { - when = lang(lng_status_lastseen_yesterday).replace(qsl("{time}"), dOnline.time().toString(qsl("hh:mm"))); - } else { - when = lang(lng_status_lastseen_date_time).replace(qsl("{date}"), dOnline.date().toString(qsl("dd.MM.yy"))).replace(qsl("{time}"), dOnline.time().toString(qsl("hh:mm"))); + return lng_status_lastseen_yesterday(lt_time, dOnline.time().toString(qsl("hh:mm"))); } - return lang(lng_status_lastseen).replace(qsl("{when}"), when); + return lng_status_lastseen_date_time(lt_date, dOnline.date().toString(qsl("dd.MM.yy")), lt_time, dOnline.time().toString(qsl("hh:mm"))); } int32 minutes = (now - online) / 60; if (!minutes) { - when = lang(lng_status_lastseen_now); - } else if (minutes == 1) { - when = lang(lng_status_lastseen_minute).arg(minutes); + return lang(lng_status_lastseen_now); } else if (minutes < 60) { - when = lang(lng_status_lastseen_minutes).arg(minutes); - } else { - int32 hours = (now - online) / 3600; - if (hours == 1) { - when = lang(lng_status_lastseen_hour).arg(hours); - } else if (hours < 12) { - when = lang(lng_status_lastseen_hours).arg(hours); - } else { - QDateTime dOnline(date(online)), dNow(date(now)); - if (dOnline.date() == dNow.date()) { - when = lang(lng_status_lastseen_today).replace(qsl("{time}"), dOnline.time().toString(qsl("hh:mm"))); - } else if (dOnline.date().addDays(1) == dNow.date()) { - when = lang(lng_status_lastseen_yesterday).replace(qsl("{time}"), dOnline.time().toString(qsl("hh:mm"))); - } else { - when = lang(lng_status_lastseen_date).replace(qsl("{date}"), dOnline.date().toString(qsl("dd.MM.yy"))); - } - } + return lng_status_lastseen_minutes(lt_count, minutes); } - return lang(lng_status_lastseen).replace(qsl("{when}"), when); + int32 hours = (now - online) / 3600; + if (hours < 12) { + return lng_status_lastseen_hours(lt_count, hours); + } + QDateTime dOnline(date(online)), dNow(date(now)); + if (dOnline.date() == dNow.date()) { + return lng_status_lastseen_today(lt_time, dOnline.time().toString(qsl("hh:mm"))); + } else if (dOnline.date().addDays(1) == dNow.date()) { + return lng_status_lastseen_yesterday(lt_time, dOnline.time().toString(qsl("hh:mm"))); + } + return lng_status_lastseen_date(lt_date, dOnline.date().toString(qsl("dd.MM.yy"))); } UserData *feedUsers(const MTPVector &users) { diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 4174616b30..811b2910c2 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -129,10 +129,11 @@ Application::Application(int &argc, char **argv) : PsApplication(argc, argv), cSetIntRetinaFactor(int32(cRetinaFactor())); } - if (!cLangFile().isEmpty()) { + if (!cLangFile().isEmpty() && QFileInfo(cLangFile()).exists()) { LangLoaderPlain loader(cLangFile()); - if (!loader.errors().isEmpty()) { - LOG(("Lang load errors: %1").arg(loader.errors())); + cSetLangErrors(loader.errors()); + if (!cLangErrors().isEmpty()) { + LOG(("Lang load errors: %1").arg(cLangErrors())); } else if (!loader.warnings().isEmpty()) { LOG(("Lang load warnings: %1").arg(loader.warnings())); } @@ -689,6 +690,10 @@ void Application::startApp() { } } } + + if (!cLangErrors().isEmpty()) { + window->showLayer(new ConfirmBox("Lang failed: " + cLangFile() + "\n\nError: " + cLangErrors(), true, lang(lng_close))); + } } void Application::socketDisconnected() { @@ -823,7 +828,7 @@ Window *Application::wnd() { return mainApp ? mainApp->window : 0; } -QString Application::lang() { +QString Application::language() { if (!lng.length()) { lng = psCurrentLanguage(); } diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h index 9839efa869..be8d2ffd0f 100644 --- a/Telegram/SourceFiles/application.h +++ b/Telegram/SourceFiles/application.h @@ -37,7 +37,7 @@ public: static Application *app(); static Window *wnd(); - static QString lang(); + static QString language(); static MainWidget *main(); void onAppUpdate(const MTPhelp_AppUpdate &response); diff --git a/Telegram/SourceFiles/boxes/aboutbox.cpp b/Telegram/SourceFiles/boxes/aboutbox.cpp index 9f955330bb..3c2eaf3bd3 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.cpp +++ b/Telegram/SourceFiles/boxes/aboutbox.cpp @@ -24,7 +24,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org AboutBox::AboutBox() : _done(this, lang(lng_about_done), st::aboutCloseButton), -_version(this, qsl("[a href=\"https://desktop.telegram.org/#changelog\"]") + textClean(lang(lng_about_version).replace(qsl("{version}"), QString::fromWCharArray(AppVersionStr))) + qsl("[/a]"), st::aboutVersion, st::defaultTextStyle), +_version(this, qsl("[a href=\"https://desktop.telegram.org/#changelog\"]") + textClean(lng_about_version(lt_version, QString::fromWCharArray(AppVersionStr))) + qsl("[/a]"), st::aboutVersion, st::defaultTextStyle), _text(this, lang(lng_about_text), st::aboutLabel, st::aboutTextStyle), _hiding(false), a_opacity(0, 1) { diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index dbc2793efe..b2b7a0ff40 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -188,7 +188,7 @@ void AddContactBox::paintEvent(QPaintEvent *e) { p.drawText(st::addContactTitlePos.x(), st::addContactTitlePos.y() + st::addContactTitleFont->ascent, _boxTitle); } else { int32 h = size().height() - st::boxPadding.top() * 2 - _retryButton.height() - st::boxPadding.bottom(); - p.drawText(QRect(st::boxPadding.left(), st::boxPadding.top(), _width - st::boxPadding.left() - st::boxPadding.right(), h), lang(lng_contact_not_joined).replace(qsl("{name}"), _sentName), style::al_topleft); + p.drawText(QRect(st::boxPadding.left(), st::boxPadding.top(), _width - st::boxPadding.left() - st::boxPadding.right(), h), lng_contact_not_joined(lt_name, _sentName), style::al_topleft); } } } else { @@ -320,7 +320,7 @@ void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { _lastInput.hide(); _phoneInput.hide(); _retryButton.show(); - int32 theight = st::addContactTitleFont->m.boundingRect(0, 0, _width - st::boxPadding.left() - st::boxPadding.right(), 1, Qt::TextWordWrap, lang(lng_contact_not_joined).replace(qsl("{name}"), _sentName)).height(); + int32 theight = st::addContactTitleFont->m.boundingRect(0, 0, _width - st::boxPadding.left() - st::boxPadding.right(), 1, Qt::TextWordWrap, lng_contact_not_joined(lt_name, _sentName)).height(); int32 h = st::boxPadding.top() * 2 + theight + _retryButton.height() + st::boxPadding.bottom(); resize(_width, h); _retryButton.move(_retryButton.x(), h - _retryButton.height()); diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index d241c6600e..16755c3be5 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -120,7 +120,7 @@ void DialogsListWidget::paintEvent(QPaintEvent *e) { } if (_state == SearchedState || !searchResults.isEmpty()) { - QString text = searchResults.isEmpty() ? lang(lng_search_no_results) : lang(searchedCount > 1 ? lng_search_n_results : lng_search_one_result).replace(qsl("{count}"), QString::number(searchedCount)); + QString text = lng_search_found_results(lt_count, searchResults.isEmpty() ? 0 : searchedCount); p.fillRect(0, 0, width(), st::searchedBarHeight, st::searchedBarBG->b); p.setFont(st::searchedBarFont->f); p.setPen(st::searchedBarColor->p); diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp index bd6d5286f9..55e0fc8844 100644 --- a/Telegram/SourceFiles/gui/text.cpp +++ b/Telegram/SourceFiles/gui/text.cpp @@ -287,7 +287,7 @@ QString textcmdStopColor() { return result.append(TextCommand).append(QChar(TextCommandNoColor)).append(TextCommand); } -const QChar *skipCommand(const QChar *from, const QChar *end, bool canLink = true) { +const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) { const QChar *result = from + 1; if (*from != TextCommand || result >= end) return from; @@ -328,6 +328,10 @@ const QChar *skipCommand(const QChar *from, const QChar *end, bool canLink = tru case TextCommandSkipBlock: result += 2; break; + + case TextCommandLangTag: + result += 1; + break; } return (result < end && *result == TextCommand) ? (result + 1) : from; } @@ -434,7 +438,7 @@ public: } bool readCommand() { - const QChar *afterCmd = skipCommand(ptr, end, links.size() < 0x7FFF); + const QChar *afterCmd = textSkipCommand(ptr, end, links.size() < 0x7FFF); if (afterCmd == ptr) { return false; } @@ -4137,7 +4141,7 @@ LinkRanges textParseLinks(const QString &text, bool rich) { } if (hashtagOffset < domainOffset) { if (hashtagOffset > nextCmd) { - const QChar *after = skipCommand(start + nextCmd, start + len); + const QChar *after = textSkipCommand(start + nextCmd, start + len); if (after > start + nextCmd && hashtagOffset < (after - start)) { nextCmd = offset = matchOffset = after - start; continue; @@ -4148,7 +4152,7 @@ LinkRanges textParseLinks(const QString &text, bool rich) { link.len = start + hashtagEnd - link.from; } else { if (domainOffset > nextCmd) { - const QChar *after = skipCommand(start + nextCmd, start + len); + const QChar *after = textSkipCommand(start + nextCmd, start + len); if (after > start + nextCmd && domainOffset < (after - start)) { nextCmd = offset = matchOffset = after - start; continue; diff --git a/Telegram/SourceFiles/gui/text.h b/Telegram/SourceFiles/gui/text.h index 76745acbc9..6b8ac28a2e 100644 --- a/Telegram/SourceFiles/gui/text.h +++ b/Telegram/SourceFiles/gui/text.h @@ -340,6 +340,8 @@ enum TextCommands { TextCommandColor = 0x09, TextCommandNoColor = 0x0A, TextCommandSkipBlock = 0x0B, + + TextCommandLangTag = 0x20, }; enum { @@ -467,3 +469,4 @@ QString textcmdLink(ushort lnkIndex, const QString &text); QString textcmdLink(const QString &url, const QString &text); QString textcmdStartColor(const style::color &color); QString textcmdStopColor(); +const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 655015e4c7..ec2ac6def1 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -943,11 +943,11 @@ bool History::updateTyping(uint64 ms, uint32 dots, bool force) { QString newTypingStr; int32 cnt = typing.size(); if (cnt > 2) { - newTypingStr = lang(lng_many_typing).replace(qsl("{n}"), QString("%1").arg(cnt)); + newTypingStr = lng_many_typing(lt_count, cnt); } else if (cnt > 1) { - newTypingStr = lang(lng_users_typing).replace(qsl("{user1}"), typing.begin().key()->firstName).replace(qsl("{user2}"), (typing.end() - 1).key()->firstName); + newTypingStr = lng_users_typing(lt_user, typing.begin().key()->firstName, lt_second_user, (typing.end() - 1).key()->firstName); } else if (cnt) { - newTypingStr = peer->chat ? lang(lng_user_typing).replace(qsl("{user}"), typing.begin().key()->firstName) : lang(lng_typing); + newTypingStr = peer->chat ? lng_user_typing(lt_user, typing.begin().key()->firstName) : lang(lng_typing); } if (!newTypingStr.isEmpty()) { newTypingStr += qsl("..."); @@ -1108,6 +1108,13 @@ Histories::Parent::iterator Histories::erase(Histories::Parent::iterator i) { return Parent::erase(i); } +void Histories::remove(const PeerId &peer) { + iterator i = find(peer); + if (i != cend()) { + erase(i); + } +} + HistoryItem *Histories::addToBack(const MTPmessage &msg, int msgState) { PeerId from_id = 0, to_id = 0; switch (msg.type()) { @@ -2353,7 +2360,7 @@ QString formatDownloadText(qint64 ready, qint64 total) { totalStr = QString::number(totalKb); mb = qsl("Kb"); } - return lang(lng_save_downloaded).replace(qsl("{ready}"), readyStr).replace(qsl("{total}"), totalStr).replace(qsl("{mb}"), mb); + return lng_save_downloaded(lt_ready, readyStr, lt_total, totalStr, lt_mb, mb); } QString formatDurationText(qint64 duration) { @@ -2362,7 +2369,7 @@ QString formatDurationText(qint64 duration) { } QString formatDurationAndSizeText(qint64 duration, qint64 size) { - return lang(lng_duration_and_size).replace(qsl("{duration}"), formatDurationText(duration)).replace(qsl("{size}"), formatSizeText(size)); + return lng_duration_and_size(lt_duration, formatDurationText(duration), lt_size, formatSizeText(size)); } int32 _downloadWidth = 0, _openWithWidth = 0, _cancelWidth = 0, _buttonWidth = 0; @@ -3772,18 +3779,6 @@ HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, const MTPD initDimensions(text); } -//HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, const MTPDgeoChatMessage &msg) : -// HistoryItem(history, block, msg.vid.v, (msg.vfrom_id.v == MTP::authedId()), false, ::date(msg.vdate), msg.vfrom_id.v), media(0), message(st::msgMinWidth) { -// QString text(qs(msg.vmessage)); -// initMedia(msg.vmedia, text); -// initDimensions(text); -//} - -//HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, bool out, bool unread, QDateTime date, int32 from, const QString &msg) : message(st::msgMinWidth), -// HistoryItem(history, block, msgId, out, unread, date, from), media(0), _text(st::msgMinWidth), _textWidth(0), _textHeight(0) { -// initDimensions(textClean(msg)); -//} - HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, bool out, bool unread, QDateTime date, int32 from, const QString &msg, const MTPMessageMedia &media) : HistoryItem(history, block, msgId, out, unread, date, from) , _text(st::msgMinWidth) @@ -4146,7 +4141,7 @@ void HistoryMessage::drawInDialog(QPainter &p, const QRect &r, bool act, const H if (_history->peer->chat || out()) { TextCustomTagsMap custom; custom.insert(QChar('c'), qMakePair(textcmdStartLink(1), textcmdStopLink())); - msg = lang(lng_message_with_from).replace(qsl("{from}"), textRichPrepare((_from == App::self()) ? lang(lng_from_you) : _from->firstName)).replace(qsl("{message}"), textRichPrepare(msg)); + msg = lng_message_with_from(lt_from, textRichPrepare((_from == App::self()) ? lang(lng_from_you) : _from->firstName), lt_message, textRichPrepare(msg)); cache.setRichText(st::dlgHistFont, msg, _textDlgOptions, custom); } else { cache.setText(st::dlgHistFont, msg, _textDlgOptions); @@ -4168,10 +4163,6 @@ QString HistoryMessage::notificationHeader() const { QString HistoryMessage::notificationText() const { QString msg(_media ? _media->inDialogsText() : _text.original(0, 0xFFFF, false)); if (msg.size() > 0xFF) msg = msg.mid(0, 0xFF) + qsl(".."); -// subtitle used -// if (_history->peer->chat || out()) { -// msg = lang(lng_message_with_from).replace(qsl("[c]"), QString()).replace(qsl("[/c]"), QString()).replace(qsl("{from}"), textRichPrepare((_from == App::self()) ? lang(lng_from_you) : _from->firstName)).replace(qsl("{message}"), textRichPrepare(msg)); -// } return msg; } @@ -4363,35 +4354,41 @@ void HistoryForwarded::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 return HistoryMessage::getSymbol(symbol, after, upon, x, y); } -QString HistoryServiceMsg::messageByAction(const MTPmessageAction &action, TextLinkPtr &second) { +void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { + TextLinkPtr second; + LangString text = lang(lng_message_empty); + QString from = textcmdLink(1, _from->name); + switch (action.type()) { case mtpc_messageActionChatAddUser: { const MTPDmessageActionChatAddUser &d(action.c_messageActionChatAddUser()); if (App::peerFromUser(d.vuser_id) == _from->id) { - return lang(lng_action_user_joined); + text = lng_action_user_joined(lt_from, from); + } else { + UserData *u = App::user(App::peerFromUser(d.vuser_id)); + second = TextLinkPtr(new PeerLink(u)); + text = lng_action_add_user(lt_from, from, lt_user, textcmdLink(2, u->name)); } - UserData *u = App::user(App::peerFromUser(d.vuser_id)); - second = TextLinkPtr(new PeerLink(u)); - return lang(lng_action_add_user).replace(qsl("{user}"), textcmdLink(2, u->name)); } break; case mtpc_messageActionChatCreate: { const MTPDmessageActionChatCreate &d(action.c_messageActionChatCreate()); - return lang(lng_action_created_chat).replace(qsl("{title}"), textClean(qs(d.vtitle))); + text = lng_action_created_chat(lt_from, from, lt_title, textClean(qs(d.vtitle))); } break; case mtpc_messageActionChatDeletePhoto: { - return lang(lng_action_removed_photo); + text = lng_action_removed_photo(lt_from, from); } break; case mtpc_messageActionChatDeleteUser: { const MTPDmessageActionChatDeleteUser &d(action.c_messageActionChatDeleteUser()); if (App::peerFromUser(d.vuser_id) == _from->id) { - return lang(lng_action_user_left); + text = lng_action_user_left(lt_from, from); + } else { + UserData *u = App::user(App::peerFromUser(d.vuser_id)); + second = TextLinkPtr(new PeerLink(u)); + text = lng_action_kick_user(lt_from, from, lt_user, textcmdLink(2, u->name)); } - UserData *u = App::user(App::peerFromUser(d.vuser_id)); - second = TextLinkPtr(new PeerLink(u)); - return lang(lng_action_kick_user).replace(qsl("{user}"), textcmdLink(2, u->name)); } break; case mtpc_messageActionChatEditPhoto: { @@ -4399,25 +4396,25 @@ QString HistoryServiceMsg::messageByAction(const MTPmessageAction &action, TextL if (d.vphoto.type() == mtpc_photo) { _media = new HistoryPhoto(history()->peer, d.vphoto.c_photo(), st::msgServicePhotoWidth); } - return lang(lng_action_changed_photo); + text = lng_action_changed_photo(lt_from, from); } break; case mtpc_messageActionChatEditTitle: { const MTPDmessageActionChatEditTitle &d(action.c_messageActionChatEditTitle()); - return lang(lng_action_changed_title).replace(qsl("{title}"), textClean(qs(d.vtitle))); + text = lng_action_changed_title(lt_from, from, lt_title, textClean(qs(d.vtitle))); } break; - case mtpc_messageActionGeoChatCheckin: { - return lang(lng_action_checked_in); - } break; - - case mtpc_messageActionGeoChatCreate: { - const MTPDmessageActionGeoChatCreate &d(action.c_messageActionGeoChatCreate()); - return lang(lng_action_created_chat).replace(qsl("{title}"), textClean(qs(d.vtitle))); - } break; + default: from = QString(); break; } - return lang(lng_message_empty); + _text.setText(st::msgServiceFont, text, _historySrvOptions); + if (!from.isEmpty()) { + _text.setLink(1, TextLinkPtr(new PeerLink(_from))); + } + if (second) { + _text.setLink(2, second); + } + return ; } HistoryServiceMsg::HistoryServiceMsg(History *history, HistoryBlock *block, const MTPDmessageService &msg) : @@ -4425,33 +4422,10 @@ HistoryServiceMsg::HistoryServiceMsg(History *history, HistoryBlock *block, cons , _text(st::msgMinWidth) , _media(0) { - - TextLinkPtr second; - QString text(messageByAction(msg.vaction, second)); - int32 fromPos = text.indexOf(qsl("{from}")); - if (fromPos >= 0) { - text = text.replace(qsl("{from}"), textcmdLink(1, _from->name)); - } - _text.setText(st::msgServiceFont, text, _historySrvOptions); - if (fromPos >= 0) { - _text.setLink(1, TextLinkPtr(new PeerLink(_from))); - } - if (second) { - _text.setLink(2, second); - } + setMessageByAction(msg.vaction); initDimensions(); } -/* -HistoryServiceMsg::HistoryServiceMsg(History *history, HistoryBlock *block, const MTPDgeoChatMessageService &msg) : - HistoryItem(history, block, msg.vid.v, (msg.vfrom_id.v == MTP::authedId()), false, ::date(msg.vdate), msg.vfrom_id.v), media(0), message(st::msgMinWidth) { - QString text(messageByAction(msg.vaction)); - text = text.replace(qsl("{from}"), _from->name); - message.setText(st::msgServiceFont, text); - _maxw = message.maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right(); - _minh = message.minHeight(); -} -/**/ HistoryServiceMsg::HistoryServiceMsg(History *history, HistoryBlock *block, MsgId msgId, QDateTime date, const QString &msg, bool out, bool unread, HistoryMedia *media) : HistoryItem(history, block, msgId, out, unread, date, 0) , _text(st::msgServiceFont, msg, _historySrvOptions, st::dlgMinWidth) @@ -4622,7 +4596,7 @@ void HistoryUnreadBar::initDimensions(const HistoryItem *parent) { void HistoryUnreadBar::setCount(int32 count) { if (!count) freezed = true; if (freezed) return; - text = lang(lng_unread_bar).arg(count); + text = lng_unread_bar(lt_count, count); } void HistoryUnreadBar::draw(QPainter &p, uint32 selection) const { diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 08f2c92169..04014d9ef4 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -491,6 +491,7 @@ struct Histories : public QHash, public Animated { void clear(); Parent::iterator erase(Parent::iterator i); + void remove(const PeerId &peer); ~Histories() { clear(); @@ -1503,8 +1504,6 @@ class HistoryMessage : public HistoryItem { public: HistoryMessage(History *history, HistoryBlock *block, const MTPDmessage &msg); -// HistoryMessage(History *history, HistoryBlock *block, const MTPDgeoChatMessage &msg); -// HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, bool out, bool unread, QDateTime date, int32 from, const QString &msg); HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, bool out, bool unread, QDateTime date, int32 from, const QString &msg, const MTPMessageMedia &media); HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, bool out, bool unread, QDateTime date, int32 from, const QString &msg, HistoryMedia *media); @@ -1598,7 +1597,6 @@ class HistoryServiceMsg : public HistoryItem { public: HistoryServiceMsg(History *history, HistoryBlock *block, const MTPDmessageService &msg); -// HistoryServiceMsg(History *history, HistoryBlock *block, const MTPDgeoChatMessageService &msg); HistoryServiceMsg(History *history, HistoryBlock *block, MsgId msgId, QDateTime date, const QString &msg, bool out = false, bool unread = false, HistoryMedia *media = 0); void initDimensions(const HistoryItem *parent = 0); @@ -1633,7 +1631,7 @@ public: protected: - QString messageByAction(const MTPmessageAction &action, TextLinkPtr &second); + void setMessageByAction(const MTPmessageAction &action); Text _text; HistoryMedia *_media; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index a1591bdb82..9fa5bf47e7 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -1489,24 +1489,25 @@ void HistoryHider::offerPeer(PeerId peer) { return; } offered = App::peer(peer); - QString phrase; + LangString phrase; + QString recipient = offered->chat ? '\xAB' + offered->name + '\xBB' : offered->name; if (_sharedContact) { - phrase = lang(lng_forward_share_contact); + phrase = lng_forward_share_contact(lt_recipient, recipient); } else if (_sendPath) { if (cSendPaths().size() > 1) { - phrase = lang(lng_forward_send_files_confirm); + phrase = lng_forward_send_files_confirm(lt_recipient, recipient); } else { QString name(QFileInfo(cSendPaths().front()).fileName()); if (name.size() > 10) { name = name.mid(0, 8) + '.' + '.'; } - phrase = lang(lng_forward_send_file_confirm).replace(qsl("{name}"), name); + phrase = lng_forward_send_file_confirm(lt_name, name, lt_recipient, recipient); } } else { - phrase = lang(lng_forward_confirm); + phrase = lng_forward_confirm(lt_recipient, recipient); } - toText.setText(st::boxFont, phrase.replace(qsl("{recipient}"), offered->chat ? '\xAB' + offered->name + '\xBB' : offered->name), _textNameOptions); + toText.setText(st::boxFont, phrase, _textNameOptions); toTextWidth = toText.maxWidth(); if (toTextWidth > box.width() - st::boxPadding.left() - st::boxPadding.right()) { toTextWidth = box.width() - st::boxPadding.left() - st::boxPadding.right(); @@ -2765,10 +2766,10 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) { int32 t = unixtime(); if (histPeer->chat) { ChatData *chat = histPeer->asChat(); - if (chat->forbidden || chat->count <= 0) { - text = lang(lng_chat_no_members); + if (chat->forbidden) { + text = lang(lng_chat_status_unaccessible); } else if (chat->participants.isEmpty()) { - text = titlePeerText.isEmpty() ? lang(lng_chat_members).arg(chat->count) : titlePeerText; + text = titlePeerText.isEmpty() ? lng_chat_status_members(lt_count, chat->count < 0 ? 0 : chat->count) : titlePeerText; } else { int32 onlineCount = 0; bool onlyMe = true; @@ -2779,9 +2780,9 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) { } } if (onlineCount && !onlyMe) { - text = lang(lng_chat_members_online).arg(chat->participants.size()).arg(onlineCount); + text = lng_chat_status_members_online(lt_count, chat->participants.size(), lt_count_online, onlineCount); } else { - text = lang(lng_chat_members).arg(chat->participants.size()); + text = lng_chat_status_members(lt_count, chat->participants.size()); } } } else { @@ -3181,6 +3182,8 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { e->ignore(); + } else if (e->key() == Qt::Key_Back) { + onCancel(); } else if (e->key() == Qt::Key_PageDown) { if ((e->modifiers() & Qt::ControlModifier) || (e->modifiers() & Qt::MetaModifier)) { PeerData *after = 0; diff --git a/Telegram/SourceFiles/intro/introcode.cpp b/Telegram/SourceFiles/intro/introcode.cpp index f338dbf53b..4fa6b92927 100644 --- a/Telegram/SourceFiles/intro/introcode.cpp +++ b/Telegram/SourceFiles/intro/introcode.cpp @@ -95,9 +95,9 @@ void IntroCode::paintEvent(QPaintEvent *e) { } QString callText = lang(lng_code_calling); if (waitTillCall >= 3600) { - callText = lang(lng_code_call).arg(QString("%1:%2").arg(waitTillCall / 3600).arg((waitTillCall / 60) % 60, 2, 10, QChar('0'))).arg(waitTillCall % 60, 2, 10, QChar('0')); + callText = lng_code_call(lt_minutes, qsl("%1:%2").arg(waitTillCall / 3600).arg((waitTillCall / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(waitTillCall % 60, 2, 10, QChar('0'))); } else if (waitTillCall > 0) { - callText = lang(lng_code_call).arg(waitTillCall / 60).arg(waitTillCall % 60, 2, 10, QChar('0')); + callText = lng_code_call(lt_minutes, QString::number(waitTillCall / 60), lt_seconds, qsl("%1").arg(waitTillCall % 60, 2, 10, QChar('0'))); } else if (waitTillCall < 0) { callText = lang(lng_code_called); } diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index 98f06deb05..46a81a96a7 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -45,7 +45,7 @@ IntroPhone::IntroPhone(IntroWidget *parent) : IntroStage(parent), next(this, lang(lng_intro_next), st::btnIntroNext), country(this, st::introCountry), phone(this, st::inpIntroPhone, lang(lng_phone_ph)), code(this, st::inpIntroCountryCode), - _signup(this, lang(lng_phone_notreg).replace(qsl("{signup}"), textcmdStartLink(1)).replace(qsl("{/signup}"), textcmdStopLink()), st::introErrLabel, st::introErrLabelTextStyle), + _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), st::introErrLabel, st::introErrLabelTextStyle), _showSignup(false) { setVisible(false); setGeometry(parent->innerRect()); @@ -227,7 +227,7 @@ void IntroPhone::phoneCheckDone(const MTPauth_CheckedPhone &result) { checkRequest.start(1000); - sentRequest = MTP::send(MTPauth_SendCode(MTP_string(sentPhone), MTP_int(0), MTP_int(ApiId), MTP_string(ApiHash), MTP_string(Application::lang())), rpcDone(&IntroPhone::phoneSubmitDone), rpcFail(&IntroPhone::phoneSubmitFail)); + sentRequest = MTP::send(MTPauth_SendCode(MTP_string(sentPhone), MTP_int(0), MTP_int(ApiId), MTP_string(ApiHash), MTP_string(Application::language())), rpcDone(&IntroPhone::phoneSubmitDone), rpcFail(&IntroPhone::phoneSubmitFail)); } else { showError(lang(lng_bad_phone_noreg), true); enableAll(true); @@ -256,7 +256,7 @@ void IntroPhone::toSignUp() { checkRequest.start(1000); - sentRequest = MTP::send(MTPauth_SendCode(MTP_string(sentPhone), MTP_int(0), MTP_int(ApiId), MTP_string(ApiHash), MTP_string(Application::lang())), rpcDone(&IntroPhone::phoneSubmitDone), rpcFail(&IntroPhone::phoneSubmitFail)); + sentRequest = MTP::send(MTPauth_SendCode(MTP_string(sentPhone), MTP_int(0), MTP_int(ApiId), MTP_string(ApiHash), MTP_string(Application::language())), rpcDone(&IntroPhone::phoneSubmitDone), rpcFail(&IntroPhone::phoneSubmitFail)); } bool IntroPhone::phoneSubmitFail(const RPCError &error) { diff --git a/Telegram/SourceFiles/lang.cpp b/Telegram/SourceFiles/lang.cpp new file mode 100644 index 0000000000..926af45311 --- /dev/null +++ b/Telegram/SourceFiles/lang.cpp @@ -0,0 +1,69 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" + +#include "lang.h" + +Qt::LayoutDirection langDir() { // current lang dependent + return Qt::LeftToRight; +} + +LangString langCounted(ushort key0, ushort tag, float64 value) { // current lang dependent + int v = qFloor(value); + QString sv; + ushort key = key0; + if (v != qCeil(value)) { + key += 2; + sv = QString::number(value); + } else { + if (v == 1) { + key += 1; + } else if (v) { + key += 2; + } + sv = QString::number(v); + } + while (key > key0) { + LangString v = lang(LangKey(key)); + if (!v.isEmpty()) return v.tag(tag, sv); + --key; + } + return lang(LangKey(key0)).tag(tag, sv); +} + +const QString &LangLoader::errors() const { + if (_errors.isEmpty() && !_err.isEmpty()) { + _errors = _err.join('\n'); + } + return _errors; +} + +const QString &LangLoader::warnings() const { + if (!_checked) { + for (int32 i = 0; i < lngkeys_cnt; ++i) { + if (!_found[i]) { + _warn.push_back(qsl("No value found for key '%1'").arg(langKeyName(LangKey(i)))); + } + } + _checked = true; + } + if (_warnings.isEmpty() && !_warn.isEmpty()) { + _warnings = _warn.join('\n'); + } + return _warnings; +} diff --git a/Telegram/SourceFiles/lang.h b/Telegram/SourceFiles/lang.h new file mode 100644 index 0000000000..28e77a9de5 --- /dev/null +++ b/Telegram/SourceFiles/lang.h @@ -0,0 +1,118 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014 John Preston, https://desktop.telegram.org +*/ +#pragma once + +class LangString : public QString { +public: + LangString() { + } + LangString(const QString &str) : QString(str) { + } + + LangString tag(ushort tag, const QString &replacement) { + for (const QChar *s = constData(), *ch = s, *e = ch + size(); ch != e;) { + if (*ch == TextCommand) { + if (ch + 3 < e && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) { + if ((ch + 2)->unicode() == 0x0020 + tag) { + LangString result; + result.reserve(size() + replacement.size() - 4); + if (ch > s) result.append(midRef(0, ch - s)); + result.append(replacement); + if (ch + 4 < e) result.append(midRef(ch - s + 4)); + return result; + } else { + ch += 4; + } + } else { + const QChar *next = textSkipCommand(ch, e); + if (next == ch) { + ++ch; + } else { + ch = next; + } + } + } else { + ++ch; + } + } + return *this; + } + + LangString &operator=(const QString &str) { + QString::operator=(str); + return (*this); + } + +}; + +LangString langCounted(ushort key0, ushort tag, float64 value); + +#include "lang_auto.h" + +const char *langKeyName(LangKey key); + +inline LangString langDayOfMonth(const QDate &date) { + int32 month = date.month(), day = date.day(); + return (month > 0 && month <= 12) ? lng_month_day(lt_month, lang(LangKey(lng_month1 + month - 1)), lt_day, QString::number(day)) : qsl("MONTH_ERR"); +} + +inline LangString langDayOfWeek(const QDate &date) { + int32 day = date.dayOfWeek(); + return (day > 0 && day <= 7) ? lang(LangKey(lng_weekday1 + day - 1)) : qsl("DAY_ERR"); +} + +inline LangString langDayOfWeekFull(const QDate &date) { + int32 day = date.dayOfWeek(); + return (day > 0 && day <= 7) ? lang(LangKey(lng_weekday1_full + day - 1)) : qsl("DAY_ERR"); +} + +Qt::LayoutDirection langDir(); + +class LangLoader { +public: + const QString &errors() const; + const QString &warnings() const; + +protected: + LangLoader() : _checked(false) { + memset(_found, 0, sizeof(_found)); + } + + ushort tagIndex(const QByteArray &tag) const; + LangKey keyIndex(const QByteArray &key) const; + bool tagReplaced(LangKey key, ushort tag) const; + LangKey subkeyIndex(LangKey key, ushort tag, ushort index) const; + + bool feedKeyValue(LangKey key, const QString &value); + + void error(const QString &text) { + _err.push_back(text); + } + void warning(const QString &text) { + _warn.push_back(text); + } + +private: + mutable QStringList _err, _warn; + mutable QString _errors, _warnings; + mutable bool _checked; + bool _found[lngkeys_cnt]; + + LangLoader(const LangLoader &); + LangLoader &operator=(const LangLoader &); +}; diff --git a/Telegram/SourceFiles/langloaderplain.cpp b/Telegram/SourceFiles/langloaderplain.cpp index 4e5743f746..43af461d89 100644 --- a/Telegram/SourceFiles/langloaderplain.cpp +++ b/Telegram/SourceFiles/langloaderplain.cpp @@ -61,47 +61,136 @@ namespace { } while (start != from); return true; } +} - bool readKeyValue(const char *&from, const char *end, QString &name, QString &value) { - if (!skipJunk(from, end)) return false; +bool LangLoaderPlain::readKeyValue(const char *&from, const char *end) { + if (!skipJunk(from, end)) return false; - const char *nameStart = from; - while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) { - ++from; - } - - QString varName = QString::fromUtf8(nameStart, from - nameStart); - - if (!skipJunk(from, end)) throw Exception("Unexpected end of file!"); - if (*from != ':') throw Exception(QString("':' expected after '%1'").arg(varName)); - - if (!skipJunk(++from, end)) throw Exception("Unexpected end of file!"); - if (*from != '"') throw Exception(QString("Expected string after '%1:'").arg(varName)); - - QByteArray varValue; - const char *start = ++from; - while (from < end && *from != '"') { - if (*from == '\\') { - if (from + 1 >= end) throw Exception("Unexpected end of file!"); - if (*(from + 1) == '"' || *(from + 1) == '\\') { - if (from > start) varValue.append(start, from - start); - start = ++from; - } - } - ++from; - } - if (from >= end) throw Exception("Unexpected end of file!"); - if (from > start) varValue.append(start, from - start); - - if (!skipJunk(++from, end)) throw Exception("Unexpected end of file!"); - if (*from != ';') throw Exception(QString("';' expected after '%1: \"value\"'").arg(varName)); - - skipJunk(++from, end); - - name = varName; - value = QString::fromUtf8(varValue); - return true; + if (*from != '"') throw Exception(QString("Expected quote after key name!")); + ++from; + const char *nameStart = from; + while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) { + ++from; } + + QByteArray varName = QByteArray(nameStart, from - nameStart); + + if (*from != '"') throw Exception(QString("Expected quote after key name '%1'!").arg(QLatin1String(varName))); + ++from; + + if (!skipJunk(from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName))); + if (*from != '=') throw Exception(QString("'=' expected in key '%1'!").arg(QLatin1String(varName))); + + if (!skipJunk(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName))); + if (*from != '"') throw Exception(QString("Expected string after '=' in key '%1'!").arg(QLatin1String(varName))); + + LangKey varKey = keyIndex(varName); + if (varKey == lngkeys_cnt) throw Exception(QString("Unknown key '%1'!").arg(QLatin1String(varName))); + + QByteArray varValue; + QMap tagsUsed; + const char *start = ++from; + while (from < end && *from != '"') { + if (*from == '\n') { + throw Exception(QString("Unexpected end of string in key '%1'!").arg(QLatin1String(varName))); + } + if (*from == '\\') { + if (from + 1 >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName))); + if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{') { + if (from > start) varValue.append(start, from - start); + start = ++from; + } else if (*(from + 1) == 'n') { + if (from > start) varValue.append(start, int(from - start)); + varValue.append('\n'); + start = (++from) + 1; + } + } else if (*from == '{') { + if (from > start) varValue.append(start, int(from - start)); + + const char *tagStart = ++from; + while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) { + ++from; + } + if (from == tagStart) throw Exception(QString("Expected tag name in key '%1'!").arg(QLatin1String(varName))); + QByteArray tagName = QByteArray(tagStart, int(from - tagStart)); + + if (from == end || (*from != '}' && *from != ':')) throw Exception(QString("Expected '}' or ':' after tag name in key '%1'!").arg(QLatin1String(varName))); + + ushort index = tagIndex(tagName); + if (index == lngtags_cnt) throw Exception(QString("Tag '%1' not found in key '%2'!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + + if (!tagReplaced(varKey, index)) throw Exception(QString("Unexpected tag '%1' in key '%2'!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + if (tagsUsed.contains(index)) throw Exception(QString("Tag '%1' double used in key '%2'!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + tagsUsed.insert(index, true); + + QString tagReplacer(4, TextCommand); + tagReplacer[1] = TextCommandLangTag; + tagReplacer[2] = QChar(0x0020 + index); + varValue.append(tagReplacer.toUtf8()); + + if (*from == ':') { + start = ++from; + + QByteArray subvarValue; + bool foundtag = false; + int countedIndex = 0; + while (from < end && *from != '"' && *from != '}') { + if (*from == '|') { + if (from > start) subvarValue.append(start, int(from - start)); + if (countedIndex >= lngtags_max_counted_values) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + if (!feedKeyValue(subkeyIndex(varKey, index, countedIndex++), QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + subvarValue = QByteArray(); + foundtag = false; + start = from + 1; + } + if (*from == '\n') { + throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + } + if (*from == '\\') { + if (from + 1 >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{' || *(from + 1) == '#') { + if (from > start) subvarValue.append(start, int(from - start)); + start = ++from; + } else if (*(from + 1) == 'n') { + if (from > start) subvarValue.append(start, int(from - start)); + + subvarValue.append('\n'); + + start = (++from) + 1; + } + } else if (*from == '{') { + throw Exception(QString("Unexpected tag inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + } else if (*from == '#') { + if (foundtag) throw Exception(QString("Replacement '#' double used inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + foundtag = true; + if (from > start) subvarValue.append(start, int(from - start)); + subvarValue.append(tagReplacer.toUtf8()); + start = from + 1; + } + ++from; + } + if (from >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(QString::fromUtf8(tagName)).arg(QString::fromUtf8(varName))); + if (*from == '"') throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(QString::fromUtf8(tagName)).arg(QString::fromUtf8(varName))); + + if (from > start) subvarValue.append(start, int(from - start)); + if (countedIndex >= lngtags_max_counted_values) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + if (!feedKeyValue(subkeyIndex(varKey, index, countedIndex++), QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(QLatin1String(tagName)).arg(QLatin1String(varName))); + } + start = from + 1; + } + ++from; + } + if (from >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName))); + if (from > start) varValue.append(start, from - start); + + if (!skipJunk(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName))); + if (*from != ';') throw Exception(QString("';' expected after \"value\" in key '%1'!").arg(QLatin1String(varName))); + + skipJunk(++from, end); + + if (!feedKeyValue(varKey, QString::fromUtf8(varValue))) throw Exception(QString("Could not write value in key '%1'!").arg(QLatin1String(varName))); + + return true; } LangLoaderPlain::LangLoaderPlain(const QString &file) { @@ -120,11 +209,7 @@ LangLoaderPlain::LangLoaderPlain(const QString &file) { try { const char *from = data.constData(), *end = data.constData() + data.size(); while (true) { - QString name, value; - if (!readKeyValue(from, end, name, value)) { - break; - } - if (!feedKeyValue(name, value)) { + if (!readKeyValue(from, end)) { break; } } diff --git a/Telegram/SourceFiles/langloaderplain.h b/Telegram/SourceFiles/langloaderplain.h index 1303cb957a..b5b1b7e2ba 100644 --- a/Telegram/SourceFiles/langloaderplain.h +++ b/Telegram/SourceFiles/langloaderplain.h @@ -24,4 +24,8 @@ public: LangLoaderPlain(const QString &file); +protected: + + bool readKeyValue(const char *&from, const char *end); + }; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index d42c473ac1..03addd3338 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -85,7 +85,7 @@ void TopBarWidget::onDeleteContact() { PeerData *p = App::main() ? App::main()->profilePeer() : 0; UserData *u = (p && !p->chat) ? p->asUser() : 0; if (u) { - ConfirmBox *box = new ConfirmBox(lang(lng_sure_delete_contact).replace(qsl("{contact}"), p->name)); + ConfirmBox *box = new ConfirmBox(lng_sure_delete_contact(lt_contact, p->name)); connect(box, SIGNAL(confirmed()), this, SLOT(onDeleteContactSure())); App::wnd()->showLayer(box); } @@ -105,7 +105,7 @@ void TopBarWidget::onDeleteAndExit() { PeerData *p = App::main() ? App::main()->profilePeer() : 0; ChatData *c = (p && p->chat) ? p->asChat() : 0; if (c) { - ConfirmBox *box = new ConfirmBox(lang(lng_sure_delete_and_exit).replace(qsl("{group}"), p->name)); + ConfirmBox *box = new ConfirmBox(lng_sure_delete_and_exit(lt_group, p->name)); connect(box, SIGNAL(confirmed()), this, SLOT(onDeleteAndExitSure())); App::wnd()->showLayer(box); } @@ -292,7 +292,7 @@ void TopBarWidget::showAll() { void TopBarWidget::showSelected(uint32 selCount) { PeerData *p = App::main() ? App::main()->profilePeer() : 0; _selCount = selCount; - _selStr = (_selCount > 0) ? lang((_selCount == 1) ? lng_selected_count_1 : lng_selected_count_5).arg(_selCount) : QString(); + _selStr = (_selCount > 0) ? lng_selected_count(lt_count, _selCount) : QString(); _selStrWidth = st::btnDefLink.font->m.width(_selStr); setCursor((!p && _selCount) ? style::cur_default : style::cur_pointer); showAll(); @@ -445,7 +445,7 @@ void MainWidget::forwardLayer(int32 forwardSelected) { } void MainWidget::deleteLayer(int32 selectedCount) { - QString str(lang((selectedCount < -1) ? lng_selected_cancel_sure_this : (selectedCount < 0 ? lng_selected_delete_sure_this : (selectedCount > 1 ? lng_selected_delete_sure_5 : lng_selected_delete_sure_1)))); + QString str((selectedCount < 0) ? lang(selectedCount < -1 ? lng_selected_cancel_sure_this : lng_selected_delete_sure_this) : lng_selected_delete_sure(lt_count, selectedCount)); ConfirmBox *box = new ConfirmBox((selectedCount < 0) ? str : str.arg(selectedCount), lang(lng_selected_delete_confirm)); if (selectedCount < 0) { connect(box, SIGNAL(confirmed()), overview ? overview : static_cast(&history), SLOT(onDeleteContextSure())); @@ -495,9 +495,10 @@ void MainWidget::dialogsActivate() { bool MainWidget::leaveChatFailed(PeerData *peer, const RPCError &e) { if (e.type() == "CHAT_ID_INVALID") { // left this chat already if ((profile && profile->peer() == peer) || (overview && overview->peer() == peer) || _stack.contains(peer) || history.peer() == peer) { - showPeer(0); + showPeer(0, 0, false, true); } dialogs.removePeer(peer); + App::histories().remove(peer->id); MTP::send(MTPmessages_DeleteHistory(peer->input, MTP_int(0)), rpcDone(&MainWidget::deleteHistoryPart, peer)); return true; } @@ -507,9 +508,10 @@ bool MainWidget::leaveChatFailed(PeerData *peer, const RPCError &e) { void MainWidget::deleteHistory(PeerData *peer, const MTPmessages_StatedMessage &result) { sentFullDataReceived(0, result); if ((profile && profile->peer() == peer) || (overview && overview->peer() == peer) || _stack.contains(peer) || history.peer() == peer) { - showPeer(0); + showPeer(0, 0, false, true); } dialogs.removePeer(peer); + App::histories().remove(peer->id); MTP::send(MTPmessages_DeleteHistory(peer->input, MTP_int(0)), rpcDone(&MainWidget::deleteHistoryPart, peer)); } @@ -2108,7 +2110,7 @@ void MainWidget::usernameResolveDone(const MTPUser &user) { bool MainWidget::usernameResolveFail(QString name, const RPCError &error) { if (error.code() == 400) { - App::wnd()->showLayer(new ConfirmBox(lang(lng_username_not_found).replace(qsl("{user}"), name), true)); + App::wnd()->showLayer(new ConfirmBox(lng_username_not_found(lt_user, name), true)); } return true; } @@ -2575,7 +2577,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { MTPPhoto photo(App::photoFromUserPhoto(MTP_int(user->id & 0xFFFFFFFF), d.vdate, d.vphoto)); HistoryMedia *media = new HistoryPhoto(photo.c_photo(), 100); if (App::history(user->id)->loadedAtBottom()) { - App::history(user->id)->addToBackService(clientMsgId(), date(d.vdate), lang(lng_action_user_photo).replace(qsl("{from}"), user->name), false, true, media); + App::history(user->id)->addToBackService(clientMsgId(), date(d.vdate), lng_action_user_photo(lt_from, user->name), false, true, media); } } } @@ -2589,7 +2591,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { UserData *user = App::userLoaded(d.vuser_id.v); if (user) { if (App::history(user->id)->loadedAtBottom()) { - App::history(user->id)->addToBackService(clientMsgId(), date(d.vdate), lang(lng_action_user_registered).replace(qsl("{from}"), user->name), false, true); + App::history(user->id)->addToBackService(clientMsgId(), date(d.vdate), lng_action_user_registered(lt_from, user->name), false, true); } } } break; @@ -2654,12 +2656,10 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { const MTPDupdateNewAuthorization &d(update.c_updateNewAuthorization()); QDateTime datetime = date(d.vdate); - QString text = lang(lng_new_authorization).replace(qsl("{name}"), App::self()->firstName); - text = text.replace(qsl("{day}"), langDayOfWeekFull(datetime.date())); - text = text.replace(qsl("{date}"), langDayOfMonth(datetime.date())); - text = text.replace(qsl("{time}"), datetime.time().toString(qsl("hh:mm"))); - text = text.replace(qsl("{device}"), qs(d.vdevice)); - text = text.replace(qsl("{location}"), qs(d.vlocation)); + QString name = App::self()->firstName; + QString day = langDayOfWeekFull(datetime.date()), date = langDayOfMonth(datetime.date()), time = datetime.time().toString(qsl("hh:mm")); + QString device = qs(d.vdevice), location = qs(d.vlocation); + LangString text = lng_new_authorization(lt_name, App::self()->firstName, lt_day, day, lt_date, date, lt_time, time, lt_device, device, lt_location, location); App::wnd()->serviceNotification(text); } break; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index c8e19a2a17..5e05fa8211 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -173,11 +173,11 @@ void MediaView::updateControls() { } QDateTime d(date(_photo ? _photo->date : _doc->date)), dNow(date(unixtime())); if (d.date() == dNow.date()) { - _dateText = lang(lng_status_lastseen_today).replace(qsl("{time}"), d.time().toString(qsl("hh:mm"))); + _dateText = lng_status_lastseen_today(lt_time, d.time().toString(qsl("hh:mm"))); } else if (d.date().addDays(1) == dNow.date()) { - _dateText = lang(lng_status_lastseen_yesterday).replace(qsl("{time}"), d.time().toString(qsl("hh:mm"))); + _dateText = lng_status_lastseen_yesterday(lt_time, d.time().toString(qsl("hh:mm"))); } else { - _dateText = lang(lng_status_lastseen_date_time).replace(qsl("{date}"), d.date().toString(qsl("dd.MM.yy"))).replace(qsl("{time}"), d.time().toString(qsl("hh:mm"))); + _dateText = lng_status_lastseen_date_time(lt_date, d.date().toString(qsl("dd.MM.yy")), lt_time, d.time().toString(qsl("hh:mm"))); } _fromName.setText(st::medviewNameFont, _from->name); updateHeader(); @@ -1299,7 +1299,7 @@ void MediaView::updateHeader() { count = _user->photosCount ? _user->photosCount : _user->photos.size(); } if (_index >= 0 && _index < count && count > 1) { - _header = lang(lng_mediaview_n_of_count).replace(qsl("{n}"), QString::number(index + 1)).replace(qsl("{count}"), QString::number(count)); + _header = lng_mediaview_n_of_count(lt_n, QString::number(index + 1), lt_count, QString::number(count)); _overview.setText(_header); if (_history) { if (_overview.isHidden()) _overview.show(); diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 33a1559b60..472fd6ad21 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -993,8 +993,8 @@ void OverviewInner::mouseReleaseEvent(QMouseEvent *e) { } void OverviewInner::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Escape) { - if (_selected.isEmpty()) { + if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) { + if (_selected.isEmpty() || e->key() == Qt::Key_Back) { App::main()->showBackFromStack(); } else { _overview->onClearSelected(); diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index 62e9ad342d..ed82e43a06 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -185,7 +185,7 @@ void ProfileInner::onUpdatePhoto() { } void ProfileInner::onClearHistory() { - ConfirmBox *box = new ConfirmBox(lang(lng_sure_delete_history).replace(qsl("{contact}"), _peer->name)); + ConfirmBox *box = new ConfirmBox(lng_sure_delete_history(lt_contact, _peer->name)); connect(box, SIGNAL(confirmed()), this, SLOT(onClearHistorySure())); App::wnd()->showLayer(box); } @@ -359,11 +359,11 @@ void ProfileInner::reorderParticipants() { } if (_peerChat->count > 0 && _participants.isEmpty() && !_loadingId) { _loadingId = MTP::send(MTPmessages_GetFullChat(App::peerToMTP(_peerChat->id).c_peerChat().vchat_id), rpcDone(&ProfileInner::gotFullChat)); - if (_onlineText.isEmpty()) _onlineText = lang(lng_chat_members).arg(_peerChat->count); + if (_onlineText.isEmpty()) _onlineText = lng_chat_status_members(lt_count, _peerChat->count); } else if (onlineCount && !onlyMe) { - _onlineText = lang(lng_chat_members_online).arg(_participants.size()).arg(onlineCount); + _onlineText = lng_chat_status_members_online(lt_count, _participants.size(), lt_count_online, onlineCount); } else { - _onlineText = lang(lng_chat_members).arg(_participants.size()); + _onlineText = lng_chat_status_members(lt_count, _participants.size()); } loadProfilePhotos(_lastPreload); } else { @@ -371,7 +371,7 @@ void ProfileInner::reorderParticipants() { if (_peerUser) { _onlineText = App::onlineText(_peerUser, t, true); } else { - _onlineText = lang(lng_chat_no_members); + _onlineText = lang(lng_chat_status_unaccessible); } } if (was != _participants.size()) { @@ -442,10 +442,10 @@ void ProfileInner::paintEvent(QPaintEvent *e) { top += st::profileButtonTop; if (_peerChat && _peerChat->forbidden) { - int32 w = st::btnShareContact.font->m.width(lang(lng_chat_no_members)); + int32 w = st::btnShareContact.font->m.width(lang(lng_profile_chat_unaccessible)); p.setFont(st::btnShareContact.font->f); p.setPen(st::profileOfflineColor->p); - p.drawText(_left + (_width - w) / 2, top + st::btnShareContact.textTop + st::btnShareContact.font->ascent, lang(lng_chat_no_members)); + p.drawText(_left + (_width - w) / 2, top + st::btnShareContact.textTop + st::btnShareContact.font->ascent, lang(lng_profile_chat_unaccessible)); } top += _shareContact.height(); @@ -620,7 +620,7 @@ void ProfileInner::mousePressEvent(QMouseEvent *e) { void ProfileInner::mouseReleaseEvent(QMouseEvent *e) { if (_kickDown && _kickDown == _kickOver) { _kickConfirm = _kickOver; - ConfirmBox *box = new ConfirmBox(lang(lng_profile_sure_kick).replace(qsl("{user}"), _kickOver->firstName)); + ConfirmBox *box = new ConfirmBox(lng_profile_sure_kick(lt_user, _kickOver->firstName)); connect(box, SIGNAL(confirmed()), this, SLOT(onKickConfirm())); App::wnd()->showLayer(box); } @@ -634,7 +634,7 @@ void ProfileInner::onKickConfirm() { } void ProfileInner::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Escape) { + if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) { App::main()->showBackFromStack(); } } @@ -873,14 +873,13 @@ void ProfileInner::showAll() { } QString ProfileInner::overviewLinkText(int32 type, int32 count) { - LangKey key = lng_keys_cnt; switch (type) { - case OverviewPhotos: key = (count > 1) ? lng_profile_photos : lng_profile_photo; break; - case OverviewVideos: key = (count > 1) ? lng_profile_videos : lng_profile_video; break; - case OverviewDocuments: key = (count > 1) ? lng_profile_documents : lng_profile_document; break; - case OverviewAudios: key = (count > 1) ? lng_profile_audios : lng_profile_audio; break; + case OverviewPhotos: return lng_profile_photos(lt_count, count); + case OverviewVideos: return lng_profile_videos(lt_count, count); + case OverviewDocuments: return lng_profile_documents(lt_count, count); + case OverviewAudios: return lng_profile_audios(lt_count, count); } - return lang(key).replace(qsl("{count}"), QString::number(count)); + return QString(); } ProfileWidget::ProfileWidget(QWidget *parent, const PeerData *peer) : QWidget(parent) diff --git a/Telegram/SourceFiles/pspecific_mac_p.mm b/Telegram/SourceFiles/pspecific_mac_p.mm index a25123b458..6e0cd9ff0e 100644 --- a/Telegram/SourceFiles/pspecific_mac_p.mm +++ b/Telegram/SourceFiles/pspecific_mac_p.mm @@ -67,17 +67,14 @@ private: NSString *_str; }; -typedef QMap ObjcLang; -ObjcLang objcLang; - QNSString objc_lang(LangKey key) { return QNSString(lang(key)); - - ObjcLang::const_iterator i = objcLang.constFind(key); - if (i == objcLang.cend()) { - i = objcLang.insert(key, lang(key)); - } - return i.value(); +} +QNSString objc_lang(LangKey key, LangTag tag1, const QString &replacement1 { + return QNSString(lang(key, tag1, replacement1)); +} +QString objcString(NSString *str) { + return QString::fromUtf8([str cStringUsingEncoding:NSUTF8StringEncoding]); } @interface ObserverHelper : NSObject { @@ -535,7 +532,7 @@ void objc_openFile(const QString &f, bool openwith) { [button setFrame:alwaysRect]; [button setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; NSTextField *goodLabel = [[NSTextField alloc] init]; - [goodLabel setStringValue:[objc_lang(lng_mac_this_app_can_open).s() stringByReplacingOccurrencesOfString:@"{file}" withString:name]]; + [goodLabel setStringValue:objc_lang(lng_mac_this_app_can_open, lngtag_file, objcString(name)).s()]; [goodLabel setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; [goodLabel setBezeled:NO]; [goodLabel setDrawsBackground:NO]; @@ -548,7 +545,7 @@ void objc_openFile(const QString &f, bool openwith) { [goodLabel setFrame:goodFrame]; NSTextField *badLabel = [[NSTextField alloc] init]; - [badLabel setStringValue:[objc_lang(lng_mac_not_known_app).s() stringByReplacingOccurrencesOfString:@"{file}" withString:name]]; + [badLabel setStringValue:objc_lang(lng_mac_not_known_app, lngtag_file, objcString(name)).s()]; [badLabel setFont:[goodLabel font]]; [badLabel setBezeled:NO]; [badLabel setDrawsBackground:NO]; @@ -578,7 +575,7 @@ void objc_openFile(const QString &f, bool openwith) { [openPanel setAllowsMultipleSelection:NO]; [openPanel setResolvesAliases:YES]; [openPanel setTitle:objc_lang(lng_mac_choose_app).s()]; - [openPanel setMessage:[objc_lang(lng_mac_choose_text).s() stringByReplacingOccurrencesOfString:@"{file}" withString:name]]; + [openPanel setMessage:objc_lang(lng_mac_choose_text, lngtag_file, objcString(name)).s()]; NSArray *appsPaths = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationDirectory inDomains:NSLocalDomainMask]; if ([appsPaths count]) [openPanel setDirectoryURL:[appsPaths firstObject]]; @@ -595,7 +592,7 @@ void objc_openFile(const QString &f, bool openwith) { OSStatus result = LSSetDefaultRoleHandlerForContentType((CFStringRef)UTI, kLSRolesAll, (CFStringRef)[[NSBundle bundleWithPath:path] bundleIdentifier]); - DEBUG_LOG(("App Info: set default handler for '%1' UTI result: %2").arg([UTI cStringUsingEncoding:NSUTF8StringEncoding]).arg(result)); + DEBUG_LOG(("App Info: set default handler for '%1' UTI result: %2").arg(objcString(UTI)).arg(result)); } [UTIs release]; @@ -631,9 +628,6 @@ void objc_start() { void objc_finish() { [_sharedDelegate release]; - if (!objcLang.isEmpty()) { - objcLang.clear(); - } } void objc_registerCustomScheme() { @@ -661,14 +655,14 @@ BOOL _execUpdater(BOOL update = YES) { [args addObject:QNSString(cDataFile()).s()]; } - DEBUG_LOG(("Application Info: executing %1 %2").arg(QString::fromUtf8([path cStringUsingEncoding:NSUTF8StringEncoding])).arg(QString::fromUtf8([[args componentsJoinedByString:@" "] cStringUsingEncoding:NSUTF8StringEncoding]))); + DEBUG_LOG(("Application Info: executing %1 %2").arg(objcString(path)).arg(objcString([[args componentsJoinedByString:@" "]))); if (![NSTask launchedTaskWithLaunchPath:path arguments:args]) { - LOG(("Task not launched while executing %1 %2").arg(QString::fromUtf8([path cStringUsingEncoding:NSUTF8StringEncoding])).arg(QString::fromUtf8([[args componentsJoinedByString:@" "] cStringUsingEncoding:NSUTF8StringEncoding]))); + LOG(("Task not launched while executing %1 %2").arg(objcString(path)).arg(objcString([args componentsJoinedByString:@" "]))); return NO; } } @catch (NSException *exception) { - LOG(("Exception caught while executing %1 %2").arg(QString::fromUtf8([path cStringUsingEncoding:NSUTF8StringEncoding])).arg(QString::fromUtf8([args cStringUsingEncoding:NSUTF8StringEncoding]))); + LOG(("Exception caught while executing %1 %2").arg(objcString(path)).arg(objcString(args))); return NO; } @finally { @@ -730,19 +724,19 @@ QString objc_downloadPath() { QString objc_currentCountry() { NSLocale *currentLocale = [NSLocale currentLocale]; // get the current locale. NSString *countryCode = [currentLocale objectForKey:NSLocaleCountryCode]; - return countryCode ? QString::fromUtf8([countryCode cStringUsingEncoding:NSUTF8StringEncoding]) : QString(); + return countryCode ? objcString(countryCode) : QString(); } QString objc_currentLang() { NSLocale *currentLocale = [NSLocale currentLocale]; // get the current locale. NSString *currentLang = [currentLocale objectForKey:NSLocaleLanguageCode]; - return currentLang ? QString::fromUtf8([currentLang cStringUsingEncoding:NSUTF8StringEncoding]) : QString(); + return currentLang ? objcString(currentLang) : QString(); } QString objc_convertFileUrl(const QString &url) { NSString *nsurl = [[[NSURL URLWithString: [NSString stringWithUTF8String: (qsl("file://") + url).toUtf8().constData()]] filePathURL] path]; if (!nsurl) return QString(); - return QString::fromUtf8([nsurl cStringUsingEncoding:NSUTF8StringEncoding]); + return objcString(nsurl); } diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 40a3cd54f0..f68457ccc9 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -28,6 +28,8 @@ QString gWorkingDir, gExeDir, gExeName; QStringList gSendPaths; QString gStartUrl; +QString gLangErrors; + QString gDialogLastPath, gDialogHelperPath; // optimize QFileDialog bool gSoundNotify = true; @@ -69,7 +71,7 @@ DBIEmojiTab gEmojiTab = dbietRecent; RecentEmojiPack gRecentEmojis; RecentEmojiPreload gRecentEmojisPreload; -QString gLangFile; +QString gLangFile = qsl("testlang.strings"); bool gRetina = false; float64 gRetinaFactor = 1.; diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index 90700a7da8..af49720ff4 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -149,6 +149,8 @@ DeclareReadSetting(QString, LangFile); DeclareSetting(QStringList, SendPaths); DeclareSetting(QString, StartUrl); +DeclareSetting(QString, LangErrors); + DeclareSetting(bool, Retina); DeclareSetting(float64, RetinaFactor); DeclareSetting(int32, IntRetinaFactor); diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp index 0da412e7e2..530f8f7cf6 100644 --- a/Telegram/SourceFiles/settingswidget.cpp +++ b/Telegram/SourceFiles/settingswidget.cpp @@ -132,7 +132,7 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent), _startMinimized(this, lang(lng_settings_start_min), cStartMinimized()), _sendToMenu(this, lang(lng_settings_add_sendto), cSendToMenu()), - _dpiAutoScale(this, lang(lng_settings_scale_auto).replace(qsl("{cur}"), scaleLabel(cScreenScale())), (cConfigScale() == dbisAuto)), + _dpiAutoScale(this, lng_settings_scale_auto(lt_cur, scaleLabel(cScreenScale())), (cConfigScale() == dbisAuto)), _dpiSlider(this, st::dpiSlider, dbisScaleCount - 1, cEvalScale(cConfigScale()) - 1), _dpiWidth1(st::dpiFont1->m.width(scaleLabel(dbisOne))), _dpiWidth2(st::dpiFont2->m.width(scaleLabel(dbisOneAndQuarter))), @@ -163,7 +163,7 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent), _imagesClearFailedWidth(st::linkFont->m.width(lang(lng_local_images_clear_failed))), // advanced - _connectionType(this, lang(lng_connection_auto)), + _connectionType(this, lng_connection_auto(lt_type, QString())), _resetSessions(this, lang(lng_settings_reset)), _logOut(this, lang(lng_settings_logout), st::btnLogout), _resetDone(false) @@ -213,7 +213,7 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent), connect(&_dpiAutoScale, SIGNAL(changed()), this, SLOT(onScaleAuto())); connect(&_dpiSlider, SIGNAL(changed(int32)), this, SLOT(onScaleChange())); - _curVersionText = lang(lng_settings_current_version).replace(qsl("{version}"), QString::fromWCharArray(AppVersionStr)) + ' '; + _curVersionText = lng_settings_current_version(lt_version, QString::fromWCharArray(AppVersionStr)) + ' '; _curVersionWidth = st::linkFont->m.width(_curVersionText); _newVersionText = lang(lng_settings_update_ready) + ' '; _newVersionWidth = st::linkFont->m.width(_newVersionText); @@ -482,7 +482,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { QString localImagesText = lang(lng_settings_no_images_cached); int32 cnt = Local::hasImages(); if (cnt) { - localImagesText = lang((cnt > 1) ? lng_settings_images_cached : lng_settings_image_cached).replace(qsl("{count}"), QString::number(cnt)).replace(qsl("{size}"), formatSizeText(Local::storageFilesSize())); + localImagesText = lng_settings_images_cached(lt_count, cnt, lt_size, formatSizeText(Local::storageFilesSize())); } p.setFont(st::linkFont->f); p.setPen(st::black->p); @@ -599,7 +599,7 @@ void SettingsInner::resizeEvent(QResizeEvent *e) { } void SettingsInner::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Escape) { + if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) { App::wnd()->showSettings(); } } @@ -677,7 +677,7 @@ void SettingsInner::updateConnectionType() { if (transport.isEmpty()) { _connectionType.setText(lang(lng_connection_auto_connecting)); } else { - _connectionType.setText(lang(lng_connection_auto).replace(qsl("{type}"), transport)); + _connectionType.setText(lng_connection_auto(lt_type, transport)); } } break; case dbictHttpProxy: _connectionType.setText(lang(lng_connection_http_proxy)); break; @@ -1215,7 +1215,7 @@ void SettingsInner::setDownloadProgress(qint64 ready, qint64 total) { qint64 readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024)); QString readyStr = QString::number(readyTenthMb / 10) + '.' + QString::number(readyTenthMb % 10); QString totalStr = QString::number(totalTenthMb / 10) + '.' + QString::number(totalTenthMb % 10); - QString res = lang(lng_settings_downloading).replace(qsl("{ready}"), readyStr).replace(qsl("{total}"), totalStr); + QString res = lng_settings_downloading(lt_ready, readyStr, lt_total, totalStr); if (_newVersionDownload != res) { _newVersionDownload = res; if (cAutoUpdate()) { diff --git a/Telegram/SourceFiles/title.cpp b/Telegram/SourceFiles/title.cpp index e069457121..bb2ab78496 100644 --- a/Telegram/SourceFiles/title.cpp +++ b/Telegram/SourceFiles/title.cpp @@ -169,7 +169,6 @@ void TitleWidget::resizeEvent(QResizeEvent *e) { p.setX(p.x() - _minimize.width()); _minimize.move(p); } - _settings.move(st::titleMenuOffset, 0); _back.move(st::titleMenuOffset, 0); _back.resize((_minimize.isHidden() ? (_update.isHidden() ? width() : _update.x()) : _minimize.x()) - st::titleMenuOffset, _back.height()); diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp index 0006735750..08a8d54c56 100644 --- a/Telegram/SourceFiles/window.cpp +++ b/Telegram/SourceFiles/window.cpp @@ -591,7 +591,7 @@ void Window::updateTitleStatus() { showConnecting(lang(lng_connecting)); } } else if (state < 0) { - showConnecting(lang(lng_reconnecting).arg((-state) / 1000), lang(lng_reconnecting_try_now)); + showConnecting(lng_reconnecting(lt_count, ((-state) / 1000) + 1), lang(lng_reconnecting_try_now)); QTimer::singleShot((-state) % 1000, this, SLOT(updateTitleStatus())); } else { hideConnecting(); diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 86c61ca874..a85200b849 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -586,7 +586,7 @@ true true - + @@ -861,6 +861,7 @@ + @@ -930,10 +931,10 @@ .\GeneratedFiles\style_auto.h "$(SolutionDir)$(Platform)\$(Configuration)Style\MetaStyle.exe" -classes_in ".\Resources\style_classes.txt" -classes_out ".\GeneratedFiles\style_classes.h" -styles_in ".\Resources\style.txt" -styles_out ".\GeneratedFiles\style_auto.h" -path_to_sprites ".\SourceFiles\art\\" - - .\GeneratedFiles\lang.h - .\GeneratedFiles\lang.cpp - "$(SolutionDir)$(Platform)\$(Configuration)Lang\MetaLang.exe" -lang_in ".\Resources\lang.txt" -lang_out ".\GeneratedFiles\lang" + + .\GeneratedFiles\lang_auto.h + .\GeneratedFiles\lang_auto.cpp + "$(SolutionDir)$(Platform)\$(Configuration)Lang\MetaLang.exe" -lang_in ".\Resources\lang.strings" -lang_out ".\GeneratedFiles\lang_auto" Moc%27ing application.h... @@ -949,7 +950,7 @@ $(QTDIR)\bin\moc.exe;%(FullPath) $(QTDIR)\bin\moc.exe;%(FullPath) - + @@ -1514,6 +1515,7 @@ $(QTDIR)\bin\moc.exe;%(FullPath) $(QTDIR)\bin\moc.exe;%(FullPath) + $(QTDIR)\bin\moc.exe;%(FullPath) diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 27bef0c35a..129edc3505 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -233,9 +233,6 @@ gui - - Generated Files - Source Files @@ -764,6 +761,12 @@ Generated Files\Release + + Generated Files + + + Source Files + @@ -832,9 +835,6 @@ Source Files - - Generated Files - Source Files @@ -844,6 +844,12 @@ mtproto + + Generated Files + + + Source Files + @@ -992,7 +998,6 @@ gui - gui @@ -1020,6 +1025,7 @@ Source Files +