From 85e6f55536c7b883d70bdd3afae651a1004cb0f3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Jun 2017 14:46:02 +0300 Subject: [PATCH] Support new plural keys format. All the old plural phrases were changed to work with the new format. --- Telegram/Resources/langs/lang.strings | 194 ++++++++++++------ Telegram/SourceFiles/boxes/confirm_box.cpp | 8 +- Telegram/SourceFiles/boxes/confirm_box.h | 2 +- Telegram/SourceFiles/boxes/contacts_box.cpp | 6 +- Telegram/SourceFiles/boxes/language_box.cpp | 6 +- Telegram/SourceFiles/boxes/stickers_box.cpp | 4 +- .../chat_helpers/stickers_list_widget.cpp | 3 +- .../SourceFiles/codegen/lang/generator.cpp | 109 ++++------ Telegram/SourceFiles/codegen/lang/generator.h | 1 - .../SourceFiles/codegen/lang/parsed_file.cpp | 137 +++++++++++-- .../SourceFiles/codegen/lang/parsed_file.h | 19 +- Telegram/SourceFiles/dialogswidget.cpp | 2 +- .../SourceFiles/history/history_message.cpp | 4 +- Telegram/SourceFiles/historywidget.cpp | 46 +++-- Telegram/SourceFiles/intro/introwidget.cpp | 19 +- .../SourceFiles/lang/lang_cloud_manager.cpp | 2 +- .../SourceFiles/lang/lang_file_parser.cpp | 2 +- Telegram/SourceFiles/lang/lang_instance.cpp | 111 ++++------ Telegram/SourceFiles/lang/lang_instance.h | 9 +- Telegram/SourceFiles/lang/lang_tag.cpp | 149 ++++++++++++-- Telegram/SourceFiles/lang/lang_tag.h | 3 +- Telegram/SourceFiles/layout.cpp | 4 +- Telegram/SourceFiles/mainwidget.cpp | 7 +- Telegram/SourceFiles/overviewwidget.cpp | 2 +- .../SourceFiles/profile/profile_cover.cpp | 22 +- .../settings/settings_privacy_controllers.cpp | 12 +- Telegram/gyp/codegen_rules.gypi | 8 +- 27 files changed, 593 insertions(+), 298 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index eeee7a1f0..206b5ea84 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -88,7 +88,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_continue" = "Continue"; "lng_close" = "Close"; "lng_connecting" = "Connecting..."; -"lng_reconnecting" = "Reconnect {count:now|in # s|in # s}..."; +"lng_reconnecting#one" = "Reconnect in {count} s..."; +"lng_reconnecting#other" = "Reconnect in {count} s..."; "lng_reconnecting_try_now" = "Try now"; "lng_status_service_notifications" = "service notifications"; @@ -102,8 +103,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "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_minutes#one" = "last seen {count} minute ago"; +"lng_status_lastseen_minutes#other" = "last seen {count} minutes ago"; +"lng_status_lastseen_hours#one" = "last seen {count} hour ago"; +"lng_status_lastseen_hours#other" = "last seen {count} 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}"; @@ -112,14 +115,20 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_status_connecting" = "connecting..."; "lng_chat_status_unaccessible" = "group is inaccessible"; -"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_chat_status_no_members" = "no members"; +"lng_chat_status_members#one" = "{count} member"; +"lng_chat_status_members#other" = "{count} members"; +"lng_chat_status_online#one" = "{count} online"; +"lng_chat_status_online#other" = "{count} online"; +"lng_chat_status_members_online" = "{members_count}, {online_count}"; "lng_channel_status" = "channel"; "lng_group_status" = "group"; -"lng_channel_members_link" = "{count:_not_used_|# member|# members}"; -"lng_channel_admins_link" = "{count:_not_used_|# administrator|# administrators}"; +"lng_channel_members_link#one" = "{count} member"; +"lng_channel_members_link#other" = "{count} members"; +"lng_channel_admins_link#one" = "{count} administrator"; +"lng_channel_admins_link#other" = "{count} administrators"; "lng_server_error" = "Internal server error."; "lng_flood_error" = "Too many tries. Please try again later."; @@ -128,7 +137,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_join_channel_error" = "Sorry, you have joined too many channels and supergroups. Please leave some before joining."; "lng_error_phone_flood" = "Sorry, you have deleted and re-created your account too many times recently. Please wait for a few days before signing up again."; "lng_error_start_minimized_passcoded" = "You have set a local passcode, so the app can't be launched minimized. App will ask you to enter the passcode before it can start working."; -"lng_error_pinned_max" = "Sorry, you can pin no more than {count:_not_used_|# chat|# chats} to the top."; +"lng_error_pinned_max#one" = "Sorry, you can pin no more than {count} chat to the top."; +"lng_error_pinned_max#other" = "Sorry, you can pin no more than {count} chats to the top."; "lng_error_public_groups_denied" = "Unfortunately, you were banned from participating in public groups.\n{more_info}"; "lng_edit_deleted" = "This message was deleted"; @@ -198,9 +208,14 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_signin_sure_reset" = "Warning!\n\nYou will lose all your chats and messages, along with any media and files you shared!\n\nDo you want to reset your account?"; "lng_signin_reset" = "Reset"; "lng_signin_reset_wait" = "Since the account {phone_number} is active and protected by a password, we will delete it in 1 week for security purposes. You can cancel this process at any time.\n\nYou’ll be able to reset your account in:\n{when}"; -"lng_signin_reset_in_days" = "{count_days:0 days|# day|# days} {count_hours:0 hours|# hour|# hours} {count_minutes:0 minutes|# minute|# minutes}"; -"lng_signin_reset_in_hours" = "{count_hours:0 hours|# hour|# hours} {count_minutes:0 minutes|# minute|# minutes}"; -"lng_signin_reset_in_minutes" = "{count_minutes:0 minutes|# minute|# minutes}"; +"lng_signin_reset_days#one" = "{count} day"; +"lng_signin_reset_days#other" = "{count} days"; +"lng_signin_reset_hours#one" = "{count} hour"; +"lng_signin_reset_hours#other" = "{count} hours"; +"lng_signin_reset_minutes#one" = "{count} minute"; +"lng_signin_reset_minutes#other" = "{count} minutes"; +"lng_signin_reset_in_days" = "{days_count} {hours_count} {minutes_count}"; +"lng_signin_reset_in_hours" = "{hours_count} {minutes_count}"; "lng_signin_reset_cancelled" = "Your recent attempts to reset this account have been cancelled by its active user. Please try again in 7 days."; "lng_signup_title" = "Your Info"; @@ -303,7 +318,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_backgrounds_header" = "Choose your new chat background"; "lng_theme_sure_keep" = "Keep this color theme?"; -"lng_theme_reverting" = "Reverting to the old color theme in {count:_not_used_|# second|# seconds}."; +"lng_theme_reverting#one" = "Reverting to the old color theme in {count} second."; +"lng_theme_reverting#other" = "Reverting to the old color theme in {count} seconds."; "lng_theme_keep_changes" = "Keep changes"; "lng_theme_revert" = "Revert"; @@ -329,8 +345,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_local_storage_title" = "Local storage"; "lng_settings_no_data_cached" = "No cached data found!"; -"lng_settings_images_cached" = "{count:_not_used_|# image|# images}, {size}"; -"lng_settings_audios_cached" = "{count:_not_used_|# voice message|# voice messages}, {size}"; +"lng_settings_images_cached#one" = "{count} image, {size}"; +"lng_settings_images_cached#other" = "{count} images, {size}"; +"lng_settings_audios_cached#one" = "{count} voice message, {size}"; +"lng_settings_audios_cached#other" = "{count} voice messages, {size}"; "lng_local_storage_clear" = "Clear all"; "lng_local_storage_clearing" = "Clearing..."; "lng_local_storage_cleared" = "Cleared!"; @@ -348,8 +366,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_passcode_autolock" = "Auto-Lock"; "lng_passcode_autolock_away" = "Auto-Lock if away for:"; "lng_passcode_autolock_inactive" = "Auto-Lock if inactive for:"; -"lng_passcode_autolock_minutes" = "{count:_not_used_|# minute|# minutes}"; -"lng_passcode_autolock_hours" = "{count:_not_used_|# hour|# hours}"; +"lng_passcode_autolock_minutes#one" = "{count} minute"; +"lng_passcode_autolock_minutes#other" = "{count} minutes"; +"lng_passcode_autolock_hours#one" = "{count} hour"; +"lng_passcode_autolock_hours#other" = "{count} hours"; "lng_passcode_enter_old" = "Enter current passcode"; "lng_passcode_enter_first" = "Enter a passcode"; "lng_passcode_enter_new" = "Enter new passcode"; @@ -455,32 +475,46 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_edit_privacy_lastseen_title" = "Last seen privacy"; "lng_edit_privacy_lastseen_description" = "You can choose who can see your last seen time:"; "lng_edit_privacy_lastseen_warning" = "Important: you won't be able to see Last Seen times for people with whom you don't share your Last Seen time. Approximate last seen will be shown instead (recently, within a week, within a month)."; -"lng_edit_privacy_lastseen_always" = "Always share with{count:| # user| # users}"; -"lng_edit_privacy_lastseen_never" = "Never share with{count:| # user| # users}"; +"lng_edit_privacy_lastseen_always_empty" = "Always share with"; +"lng_edit_privacy_lastseen_always#one" = "Always share with {count} user"; +"lng_edit_privacy_lastseen_always#other" = "Always share with {count} users"; +"lng_edit_privacy_lastseen_never_empty" = "Never share with"; +"lng_edit_privacy_lastseen_never#one" = "Never share with {count} user"; +"lng_edit_privacy_lastseen_never#other" = "Never share with {count} users"; "lng_edit_privacy_lastseen_exceptions" = "These settings will override the values above."; "lng_edit_privacy_lastseen_always_title" = "Always share with"; "lng_edit_privacy_lastseen_never_title" = "Never share with"; "lng_edit_privacy_groups_title" = "Group invite settings"; "lng_edit_privacy_groups_description" = "You can choose who can add you to groups and channels with granular precision:"; -"lng_edit_privacy_groups_always" = "Always allow{count:| # user| # users}"; -"lng_edit_privacy_groups_never" = "Never allow{count:| # user| # users}"; +"lng_edit_privacy_groups_always_empty" = "Always allow"; +"lng_edit_privacy_groups_always#one" = "Always allow {count} user"; +"lng_edit_privacy_groups_always#other" = "Always allow {count} users"; +"lng_edit_privacy_groups_never_empty" = "Never allow"; +"lng_edit_privacy_groups_never#one" = "Never allow {count} user"; +"lng_edit_privacy_groups_never#other" = "Never allow {count} users"; "lng_edit_privacy_groups_exceptions" = "These users will or will not be able to add you to groups and channels regardless of the settings above."; "lng_edit_privacy_groups_always_title" = "Always allow"; "lng_edit_privacy_groups_never_title" = "Never allow"; "lng_edit_privacy_calls_title" = "Phone calls privacy"; "lng_edit_privacy_calls_description" = "You can restrict who can call you:"; -"lng_edit_privacy_calls_always" = "Always allow{count:| # user| # users}"; -"lng_edit_privacy_calls_never" = "Never allow{count:| # user| # users}"; +"lng_edit_privacy_calls_always_empty" = "Always allow"; +"lng_edit_privacy_calls_always#one" = "Always allow {count} user"; +"lng_edit_privacy_calls_always#other" = "Always allow {count} users"; +"lng_edit_privacy_calls_never_empty" = "Never allow"; +"lng_edit_privacy_calls_never#one" = "Never allow {count} user"; +"lng_edit_privacy_calls_never#other" = "Never allow {count} users"; "lng_edit_privacy_calls_exceptions" = "These users will or will not be able to call you regardless of the settings above."; "lng_edit_privacy_calls_always_title" = "Always allow"; "lng_edit_privacy_calls_never_title" = "Never allow"; "lng_self_destruct_title" = "Account self-destruction"; "lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts."; -"lng_self_destruct_months" = "{count:_not_used_|# month|# months}"; -"lng_self_destruct_years" = "{count:_not_used_|# year|# years}"; +"lng_self_destruct_months#one" = "{count} month"; +"lng_self_destruct_months#other" = "{count} months"; +"lng_self_destruct_years#one" = "{count} year"; +"lng_self_destruct_years#other" = "{count} years"; "lng_change_phone_title" = "Change phone number"; "lng_change_phone_description" = "You can change your Telegram number\nhere. Your account and all your cloud data\n— messages, media, contacts, etc. will be\nmoved to the new number.\n\n[b]Important[/b]: all your Telegram contacts will\nget your [b]new number[/b] added to their address\nbook, provided they had your old number and\nyou haven't blocked them in Telegram."; @@ -508,7 +542,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_profile_edit_public_link" = "Edit public link"; "lng_profile_manage_admins" = "Manage administrators"; "lng_profile_manage_blocklist" = "Manage blocked users"; -"lng_profile_common_groups" = "{count:_not_used_|# group|# groups} in common"; +"lng_profile_common_groups#one" = "{count} group in common"; +"lng_profile_common_groups#other" = "{count} groups in common"; "lng_profile_common_groups_section" = "Groups in common"; "lng_profile_participants_section" = "Members"; "lng_profile_info_section" = "Info"; @@ -548,17 +583,23 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "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#one" = "{count} photo"; +"lng_profile_photos#other" = "{count} photos"; "lng_profile_photos_header" = "Photos"; -"lng_profile_videos" = "{count:_not_used_|# video|# videos}"; +"lng_profile_videos#one" = "{count} video"; +"lng_profile_videos#other" = "{count} videos"; "lng_profile_videos_header" = "Videos"; -"lng_profile_songs" = "{count:_not_used_|# audio file|# audio files}"; +"lng_profile_songs#one" = "{count} audio file"; +"lng_profile_songs#other" = "{count} audio files"; "lng_profile_songs_header" = "Audio files"; -"lng_profile_files" = "{count:_not_used_|# file|# files}"; +"lng_profile_files#one" = "{count} file"; +"lng_profile_files#other" = "{count} files"; "lng_profile_files_header" = "Files"; -"lng_profile_audios" = "{count:_not_used_|# voice message|# voice messages}"; +"lng_profile_audios#one" = "{count} voice message"; +"lng_profile_audios#other" = "{count} voice messages"; "lng_profile_audios_header" = "Voice messages"; -"lng_profile_shared_links" = "{count:_not_used_|# shared link|# shared links}"; +"lng_profile_shared_links#one" = "{count} shared link"; +"lng_profile_shared_links#other" = "{count} shared links"; "lng_profile_shared_links_header" = "Shared links"; "lng_profile_copy_phone" = "Copy Phone Number"; "lng_profile_copy_fullname" = "Copy Name"; @@ -581,7 +622,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_channel_add_admins" = "New administrator"; "lng_channel_add_members" = "Add members"; "lng_channel_members" = "Members"; -"lng_channel_only_last_shown" = "Only the last {count:_not_used_|# member is|# members are} shown here"; +"lng_channel_only_last_shown#one" = "Only the last {count} member is shown here"; +"lng_channel_only_last_shown#other" = "Only the last {count} members are shown here"; "lng_channel_admins" = "Administrators"; "lng_channel_add_admin" = "Add Administrator"; "lng_channel_admin_sure" = "Add {user} to administrators?"; @@ -595,7 +637,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_participant_filter" = "Search"; "lng_participant_invite" = "Invite"; -"lng_participant_invite_sorry" = "Sorry, you can only add the first {count:_not_used|# member|# members} to a channel personally.\n\nFrom now on, people will need to join via your invite link."; +"lng_participant_invite_sorry#one" = "Sorry, you can only add the first {count} member to a channel personally.\n\nFrom now on, people will need to join via your invite link."; +"lng_participant_invite_sorry#other" = "Sorry, you can only add the first {count} members to a channel personally.\n\nFrom now on, people will need to join via your invite link."; "lng_create_group_back" = "Back"; "lng_create_group_next" = "Next"; "lng_create_group_create" = "Create"; @@ -637,8 +680,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_message_empty" = "Empty Message"; "lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the last version in Settings or install it from {link}"; -"lng_duration_seconds" = "{count:_not_used_|# second|# seconds}"; -"lng_duration_minutes_seconds" = "{count_minutes:# min|# min|# min} {count_seconds:# sec|# sec|# sec}"; +"lng_duration_seconds#one" = "{count} second"; +"lng_duration_seconds#other" = "{count} seconds"; +"lng_duration_minsec_minutes#one" = "{count} min"; +"lng_duration_minsec_minutes#other" = "{count} min"; +"lng_duration_minsec_seconds#one" = "{count} sec"; +"lng_duration_minsec_seconds#other" = "{count} sec"; +"lng_duration_minutes_seconds" = "{minutes_count} {seconds_count}"; "lng_action_add_user" = "{from} added {user}"; "lng_action_add_users_many" = "{from} added {users}"; @@ -676,18 +724,24 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_action_pinned_media_sticker" = "a sticker"; "lng_action_pinned_media_emoji_sticker" = "a {emoji} sticker"; "lng_action_pinned_media_game" = "the game «{game}»"; -"lng_action_game_score" = "{from} scored {count:#|#|#} in {game}"; -"lng_action_game_you_scored" = "You scored {count:#|#|#} in {game}"; -"lng_action_game_score_no_game" = "{from} scored {count:#|#|#}"; -"lng_action_game_you_scored_no_game" = "You scored {count:#|#|#}"; +"lng_action_game_score#one" = "{from} scored {count} in {game}"; +"lng_action_game_score#other" = "{from} scored {count} in {game}"; +"lng_action_game_you_scored#one" = "You scored {count} in {game}"; +"lng_action_game_you_scored#other" = "You scored {count} in {game}"; +"lng_action_game_score_no_game#one" = "{from} scored {count}"; +"lng_action_game_score_no_game#other" = "{from} scored {count}"; +"lng_action_game_you_scored_no_game#one" = "You scored {count}"; +"lng_action_game_you_scored_no_game#other" = "You scored {count}"; "lng_action_payment_done" = "You have just successfully transferred {amount} to {user}"; "lng_action_payment_done_for" = "You have just successfully transferred {amount} to {user} for {invoice}"; -"lng_profile_migrate_reached" = "{count:_not_used_|# member|# members} limit reached"; +"lng_profile_migrate_reached#one" = "{count} member limit reached"; +"lng_profile_migrate_reached#other" = "{count} members limit reached"; "lng_profile_migrate_body" = "To get over this limit, you can upgrade your group to a supergroup."; "lng_profile_migrate_learn_more" = "Learn more »"; "lng_profile_migrate_about" = "If you'd like to go over this limit, you can upgrade your group to a supergroup. In supergroups:"; -"lng_profile_migrate_feature1" = "— The members limit is {count:_not_used_|# user|# users}"; +"lng_profile_migrate_feature1#one" = "— The members limit is {count} user"; +"lng_profile_migrate_feature1#other" = "— The members limit is {count} users"; "lng_profile_migrate_feature2" = "— New members see the entire chat history"; "lng_profile_migrate_feature3" = "— Admins delete messages for everyone"; "lng_profile_migrate_feature4" = "— Notifications are muted by default"; @@ -702,7 +756,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_profile_convert_feature4" = "— Creator can set a public link for the group"; "lng_profile_convert_warning" = "{bold_start}Note:{bold_end} This action can not be undone"; "lng_profile_convert_confirm" = "Convert"; -"lng_profile_add_more_after_upgrade" = "You will be able to add up to {count:_not_used_|# member|# members} after you upgrade your group to a supergroup."; +"lng_profile_add_more_after_upgrade#one" = "You will be able to add up to {count} member after you upgrade your group to a supergroup."; +"lng_profile_add_more_after_upgrade#other" = "You will be able to add up to {count} members after you upgrade your group to a supergroup."; "lng_channel_not_accessible" = "Sorry, this channel is not accessible."; "lng_group_not_accessible" = "Sorry, this group is not accessible."; @@ -717,7 +772,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_group_invite_want_join_channel" = "Do you want to join the channel «{title}»?"; "lng_group_invite_join" = "Join"; -"lng_group_invite_members" = "{count:_not_used_|# member|# members}, among them:"; +"lng_group_invite_members#one" = "{count} member, among them:"; +"lng_group_invite_members#other" = "{count} members, among them:"; "lng_group_invite_link" = "Invite link:"; "lng_group_invite_create" = "Create an invite link"; @@ -810,7 +866,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_stickers_you_have" = "Manage and reorder sticker packs"; "lng_stickers_featured" = "Trending Stickers"; "lng_stickers_return" = "Undo"; -"lng_stickers_count" = "{count:Loading...|# sticker|# stickers}"; +"lng_stickers_count#one" = "{count} sticker"; +"lng_stickers_count#other" = "{count} stickers"; "lng_stickers_masks_pack" = "This is a pack of mask stickers. You can use them in the photo editor on our mobile apps."; "lng_in_dlg_photo" = "Photo"; @@ -885,11 +942,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "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_many_typing#one" = "{count} is typing"; +"lng_many_typing#other" = "{count} are typing"; "lng_playing_game" = "playing a game"; "lng_user_playing_game" = "{user} is playing a game"; "lng_users_playing_game" = "{user} and {second_user} are playing a game"; -"lng_many_playing_game" = "{count:_not_used_|# is|# are} playing a game"; +"lng_many_playing_game#one" = "{count} is playing a game"; +"lng_many_playing_game#other" = "{count} are playing a game"; "lng_send_action_record_video" = "recording a video"; "lng_user_action_record_video" = "{user} is recording a video"; "lng_send_action_upload_video" = "sending a video"; @@ -910,7 +969,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_user_action_geo_location" = "{user} is picking a location"; "lng_send_action_choose_contact" = "choosing a contact"; "lng_user_action_choose_contact" = "{user} is choosing a contact"; -"lng_unread_bar" = "{count:_not_used_|# unread message|# unread messages}"; +"lng_unread_bar#one" = "{count} unread message"; +"lng_unread_bar#other" = "{count} unread messages"; "lng_maps_point" = "Location"; "lng_save_photo" = "Save image"; @@ -977,15 +1037,20 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "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_images_compress" = "Compress {count:_not_used_|image|images}"; +"lng_send_images_compress#one" = "Compress image"; +"lng_send_images_compress#other" = "Compress images"; "lng_send_image_non_local" = "Could not send a non local file: {name}"; "lng_send_image_empty" = "Could not send an empty file: {name}"; "lng_send_image_too_large" = "Could not send a file, because it is larger than 1500 MB: {name}"; "lng_send_folder" = "Could not send «{name}» because it is a directory :("; -"lng_send_images_selected" = "{count:_not_used_|# image|# images} selected"; -"lng_send_photos" = "Send {count:_not_used_|# photo|# photos}"; -"lng_send_files_selected" = "{count:_not_used_|# file|# files} selected"; -"lng_send_files" = "Send {count:_not_used_|# file|# files}"; +"lng_send_images_selected#one" = "{count} image selected"; +"lng_send_images_selected#other" = "{count} images selected"; +"lng_send_photos#one" = "Send {count} photo"; +"lng_send_photos#other" = "Send {count} photos"; +"lng_send_files_selected#one" = "{count} file selected"; +"lng_send_files_selected#other" = "{count} files selected"; +"lng_send_files#one" = "Send {count} file"; +"lng_send_files#other" = "Send {count} files"; "lng_forward_choose" = "Choose recipient..."; "lng_forward_cant" = "Sorry, no way to forward here :("; @@ -994,8 +1059,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_forward_send_file_confirm" = "Send «{name}» to {recipient}?"; "lng_forward_send_files_confirm" = "Send selected files to {recipient}?"; "lng_forward_send" = "Send"; -"lng_forward_messages" = "{count:_not_used_|Forwarded message|# forwarded messages}"; -"lng_forwarding_from" = "{user} and {count:_not_used_|# other|# others}"; +"lng_forward_messages#one" = "{count} forwarded message"; +"lng_forward_messages#other" = "{count} forwarded messages"; +"lng_forwarding_from#one" = "{user} and {count} other"; +"lng_forwarding_from#other" = "{user} and {count} others"; "lng_forwarding_from_two" = "{user} and {second_user}"; "lng_inline_switch_choose" = "Choose conversation..."; "lng_inline_switch_cant" = "Sorry, no way to write here :("; @@ -1041,15 +1108,20 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_selected_clear" = "Cancel"; "lng_selected_delete" = "Delete"; "lng_selected_forward" = "Forward"; -"lng_selected_count" = "{count:_not_used_|# message|# messages}"; +"lng_selected_count#one" = "{count} message"; +"lng_selected_count#other" = "{count} messages"; "lng_selected_cancel_sure_this" = "Cancel uploading?"; "lng_selected_upload_stop" = "Stop"; "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_sure#one" = "Do you want to delete {count} message?"; +"lng_selected_delete_sure#other" = "Do you want to delete {count} messages?"; "lng_delete_photo_sure" = "Do you want to delete this photo?"; -"lng_delete_for_everyone_hint" = "This will delete {count:_not_used_|it|them} for everyone in this chat."; -"lng_delete_for_me_chat_hint" = "This will delete {count:_not_used_|it|them} just for you, not for other participants of the chat."; -"lng_delete_for_me_hint" = "This will delete {count:_not_used_|it|them} just for you."; +"lng_delete_for_everyone_hint#one" = "This will delete it for everyone in this chat."; +"lng_delete_for_everyone_hint#other" = "This will delete them for everyone in this chat."; +"lng_delete_for_me_chat_hint#one" = "This will delete it just for you, not for other participants of the chat."; +"lng_delete_for_me_chat_hint#other" = "This will delete them just for you, not for other participants of the chat."; +"lng_delete_for_me_hint#one" = "This will delete it just for you."; +"lng_delete_for_me_hint#other" = "This will delete them just for you."; "lng_delete_for_everyone_check" = "Delete for everyone"; "lng_delete_for_other_check" = "Delete for {user}"; "lng_box_delete" = "Delete"; @@ -1061,7 +1133,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_about_text_3" = "Visit {faq_open}Telegram FAQ{faq_close} for more info."; "lng_about_done" = "Done"; -"lng_search_found_results" = "{count:No messages found|Found # message|Found # messages}"; +"lng_search_no_results" = "No messages found"; +"lng_search_found_results#one" = "Found {count} message"; +"lng_search_found_results#other" = "Found {count} messages"; "lng_search_global_results" = "Global search results"; "lng_media_save_progress" = "{ready} of {total} {mb}"; diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index 11f30dcdb..adc65a5f9 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -547,14 +547,18 @@ void DeleteMessagesBox::deleteAndClear() { Ui::hideLayer(); } -ConfirmInviteBox::ConfirmInviteBox(QWidget*, const QString &title, const MTPChatPhoto &photo, int count, const QVector &participants) +ConfirmInviteBox::ConfirmInviteBox(QWidget*, const QString &title, bool isChannel, const MTPChatPhoto &photo, int count, const QVector &participants) : _title(this, st::confirmInviteTitle) , _status(this, st::confirmInviteStatus) , _participants(participants) { _title->setText(title); QString status; if (_participants.isEmpty() || _participants.size() >= count) { - status = lng_chat_status_members(lt_count, count); + if (count > 0) { + status = lng_chat_status_members(lt_count, count); + } else { + status = lang(isChannel ? lng_channel_status : lng_group_status); + } } else { status = lng_group_invite_members(lt_count, count); } diff --git a/Telegram/SourceFiles/boxes/confirm_box.h b/Telegram/SourceFiles/boxes/confirm_box.h index be9f0ded0..d41c6b774 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.h +++ b/Telegram/SourceFiles/boxes/confirm_box.h @@ -200,7 +200,7 @@ private: class ConfirmInviteBox : public BoxContent, public RPCSender { public: - ConfirmInviteBox(QWidget*, const QString &title, const MTPChatPhoto &photo, int count, const QVector &participants); + ConfirmInviteBox(QWidget*, const QString &title, bool isChannel, const MTPChatPhoto &photo, int count, const QVector &participants); protected: void prepare() override; diff --git a/Telegram/SourceFiles/boxes/contacts_box.cpp b/Telegram/SourceFiles/boxes/contacts_box.cpp index e415ad866..9c18b9f51 100644 --- a/Telegram/SourceFiles/boxes/contacts_box.cpp +++ b/Telegram/SourceFiles/boxes/contacts_box.cpp @@ -898,11 +898,13 @@ ContactsBox::Inner::ContactData *ContactsBox::Inner::contactData(Dialogs::Row *r data->statusText = App::onlineText(peer->asUser(), _time); data->statusHasOnlineColor = App::onlineColorUse(peer->asUser(), _time); } else if (peer->isChat()) { - ChatData *chat = peer->asChat(); + auto chat = peer->asChat(); if (!chat->amIn()) { data->statusText = lang(lng_chat_status_unaccessible); - } else { + } else if (chat->count > 0) { data->statusText = lng_chat_status_members(lt_count, chat->count); + } else { + data->statusText = lang(lng_group_status); } } else if (peer->isMegagroup()) { data->statusText = lang(lng_group_status); diff --git a/Telegram/SourceFiles/boxes/language_box.cpp b/Telegram/SourceFiles/boxes/language_box.cpp index c11643089..7f9688eea 100644 --- a/Telegram/SourceFiles/boxes/language_box.cpp +++ b/Telegram/SourceFiles/boxes/language_box.cpp @@ -86,7 +86,11 @@ void LanguageBox::Inner::languageChanged(int languageIndex) { activateCurrent(); auto languageId = (*_languages)[languageIndex].id; - Lang::CurrentCloudManager().switchToLanguage(languageId); + if (Lang::Current().id() != languageId) { + // "custom" is applied each time it is passed to switchToLanguage(). + // So we check that the language really has changed. + Lang::CurrentCloudManager().switchToLanguage(languageId); + } } void LanguageBox::Inner::activateCurrent() { diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index fb64dc937..61610a909 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -713,9 +713,11 @@ void StickersBox::Inner::paintRow(Painter &p, int index, TimeMs ms) { } } + auto statusText = (s->count > 0) ? lng_stickers_count(lt_count, s->count) : lang(lng_contacts_loading); + p.setFont(st::contactsStatusFont); p.setPen(st::contactsStatusFg); - p.drawTextLeft(statusx, statusy, width(), lng_stickers_count(lt_count, s->count)); + p.drawTextLeft(statusx, statusy, width(), statusText); p.setOpacity(1); if (xadd || yadd) p.translate(-xadd, -yadd); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index c861bee54..29788b088 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -676,9 +676,10 @@ void StickersListWidget::paintFeaturedStickers(Painter &p, QRect clip) { } } + auto statusText = (size > 0) ? lng_stickers_count(lt_count, size) : lang(lng_contacts_loading); p.setFont(st::stickersTrendingSubheaderFont); p.setPen(st::stickersTrendingSubheaderFg); - p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, y + st::stickersTrendingSubheaderTop, width(), lng_stickers_count(lt_count, size)); + p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, y + st::stickersTrendingSubheaderTop, width(), statusText); y += st::stickersTrendingHeader; if (y >= clip.y() + clip.height()) break; diff --git a/Telegram/SourceFiles/codegen/lang/generator.cpp b/Telegram/SourceFiles/codegen/lang/generator.cpp index e0fa74a08..e8e4ad391 100644 --- a/Telegram/SourceFiles/codegen/lang/generator.cpp +++ b/Telegram/SourceFiles/codegen/lang/generator.cpp @@ -31,8 +31,6 @@ namespace codegen { namespace lang { namespace { -constexpr auto kMaxPluralVariants = 6; - char hexChar(uchar ch) { if (ch < 10) { return '0' + ch; @@ -120,7 +118,6 @@ bool Generator::writeHeader() { header_->include("lang/lang_tag.h").newline().pushNamespace("Lang").stream() << "\ \n\ constexpr auto kTagsCount = " << langpack_.tags.size() << ";\n\ -constexpr auto kTagsPluralVariants = " << kMaxPluralVariants << ";\n\ \n"; header_->popNamespace().newline(); @@ -141,21 +138,26 @@ enum LangKey {\n"; \n\ QString lang(LangKey key);\n\ \n"; + for (auto &entry : langpack_.entries) { - if (!entry.tags.empty()) { - auto &key = entry.key; - auto params = QStringList(); - auto applyTags = QStringList(); - for (auto &tagData : entry.tags) { - auto &tag = tagData.tag; - auto isPlural = isTagPlural(key, tag); - params.push_back("lngtag_" + tag + ", " + (isPlural ? "float64 " : "const QString &") + tag + "__val"); - applyTags.push_back("\tresult = Lang::Tag(result, lt_" + tag + ", " + (isPlural ? ("Lang::Plural(" + key + "__" + tag + "0, lt_" + tag + ", " + tag + "__val)") : (tag + "__val")) + ");"); + auto isPlural = !entry.keyBase.isEmpty(); + auto &key = entry.key; + auto params = QStringList(); + auto applyTags = QStringList(); + for (auto &tagData : entry.tags) { + auto &tag = tagData.tag; + auto isPluralTag = isPlural && (tag == kPluralTag); + params.push_back("lngtag_" + tag + ", " + (isPluralTag ? "float64 " : "const QString &") + tag + "__val"); + if (!isPluralTag) { + applyTags.push_back("\tresult = Lang::Tag(result, lt_" + tag + ", " + tag + "__val);\n"); } + } + if (!entry.tags.empty() && (!isPlural || key == ComputePluralKey(entry.keyBase, 0))) { + auto initialString = isPlural ? ("Lang::Plural(" + key + ", lt_" + kPluralTag + ", " + kPluralTag + "__val)") : ("lang(" + getFullKey(entry) + ")"); header_->stream() << "\ -inline QString " << entry.key << "(" << params.join(QString(", ")) << ") {\n\ - auto result = lang(" << entry.key << "__tagged);\n\ -" << applyTags.join('\n') << ";\n\ +inline QString " << (isPlural ? entry.keyBase : key) << "(" << params.join(QString(", ")) << ") {\n\ + auto result = " << initialString << ";\n\ +" << applyTags.join(QString()) << "\ return result;\n\ }\n\ \n"; @@ -167,7 +169,6 @@ inline QString " << entry.key << "(" << params.join(QString(", ")) << ") {\n\ const char *GetKeyName(LangKey key);\n\ ushort GetTagIndex(QLatin1String tag);\n\ LangKey GetKeyIndex(QLatin1String key);\n\ -LangKey GetSubkeyIndex(LangKey key, ushort tag, ushort index);\n\ bool IsTagReplaced(LangKey key, ushort tag);\n\ QString GetOriginalValue(LangKey key);\n\ \n"; @@ -258,15 +259,19 @@ LangKey GetKeyIndex(QLatin1String key) {\n\ auto taggedKeys = std::map(); auto keysSet = std::set>(); for (auto &entry : langpack_.entries) { - if (entry.key.mid(0, entry.key.size() - 1).endsWith("__count")) { - continue; + if (!entry.keyBase.isEmpty()) { + for (auto i = 0; i != kPluralPartCount; ++i) { + auto keyName = entry.keyBase + '#' + kPluralParts[i]; + taggedKeys.emplace(keyName, ComputePluralKey(entry.keyBase, i)); + keysSet.insert(keyName); + } + } else { + auto full = getFullKey(entry); + if (full != entry.key) { + taggedKeys.emplace(entry.key, full); + } + keysSet.insert(entry.key); } - - auto full = getFullKey(entry); - if (full != entry.key) { - taggedKeys.emplace(entry.key, full); - } - keysSet.insert(entry.key); } writeSetSearch(keysSet, [&taggedKeys](const QString &key) { @@ -277,44 +282,28 @@ LangKey GetKeyIndex(QLatin1String key) {\n\ source_->stream() << "\ }\n\ \n\ -LangKey GetSubkeyIndex(LangKey key, ushort tag, ushort index) {\n\ - if (index >= kTagsPluralVariants) return kLangKeysCount;\n\ -\n\ - switch (key) {\n"; - - for (auto &entry : langpack_.entries) { - auto cases = QString(); - for (auto &tag : entry.tags) { - if (isTagPlural(entry.key, tag.tag)) { - cases += "\t\t\tcase lt_" + tag.tag + ": return LangKey(" + entry.key + "__" + tag.tag + "0 + index);\n"; - } - } - if (cases.isEmpty()) { - continue; - } - source_->stream() << "\ - case " << entry.key << "__tagged: {\n\ - switch (tag) {\n\ -" << cases << "\ - }\n\ - } break;\n"; - } - - source_->stream() << "\ - }\n\ -\n\ - return kLangKeysCount;\n\ -}\n\ -\n\ bool IsTagReplaced(LangKey key, ushort tag) {\n\ switch (key) {\n"; + auto lastWrittenPluralEntry = QString(); for (auto &entry : langpack_.entries) { if (entry.tags.empty()) { continue; } + if (!entry.keyBase.isEmpty()) { + if (entry.keyBase == lastWrittenPluralEntry) { + continue; + } + lastWrittenPluralEntry = entry.keyBase; + for (auto i = 0; i != kPluralPartCount; ++i) { + source_->stream() << "\ + case " << ComputePluralKey(entry.keyBase, i) << ":" << ((i + 1 == kPluralPartCount) ? " {" : "") << "\n"; + } + } else { + source_->stream() << "\ + case " << getFullKey(entry) << ": {\n"; + } source_->stream() << "\ - case " << entry.key << "__tagged: {\n\ switch (tag) {\n"; for (auto &tag : entry.tags) { source_->stream() << "\ @@ -480,21 +469,11 @@ void Generator::writeSetSearch(const std::set> &s } QString Generator::getFullKey(const LangPack::Entry &entry) { - if (entry.tags.empty()) { + if (!entry.keyBase.isEmpty() || entry.tags.empty()) { return entry.key; } return entry.key + "__tagged"; } -bool Generator::isTagPlural(const QString &key, const QString &tag) const { - auto searchForKey = key + "__" + tag + "0"; - for (auto &entry : langpack_.entries) { - if (entry.key == searchForKey) { - return true; - } - } - return false; -} - } // namespace lang } // namespace codegen diff --git a/Telegram/SourceFiles/codegen/lang/generator.h b/Telegram/SourceFiles/codegen/lang/generator.h index 549a464e0..e5f868176 100644 --- a/Telegram/SourceFiles/codegen/lang/generator.h +++ b/Telegram/SourceFiles/codegen/lang/generator.h @@ -43,7 +43,6 @@ public: private: QString getFullKey(const LangPack::Entry &entry); - bool isTagPlural(const QString &key, const QString &tag) const; template void writeSetSearch(const std::set> &set, ComputeResult computeResult, const QString &invalidResult); diff --git a/Telegram/SourceFiles/codegen/lang/parsed_file.cpp b/Telegram/SourceFiles/codegen/lang/parsed_file.cpp index 93b5ee243..5ad9f61a8 100644 --- a/Telegram/SourceFiles/codegen/lang/parsed_file.cpp +++ b/Telegram/SourceFiles/codegen/lang/parsed_file.cpp @@ -46,7 +46,7 @@ bool ValidateAnsiString(const QString &value) { } bool ValidateKey(const QString &key) { - static const auto validator = QRegularExpression("^[a-z0-9_.-]+$", QRegularExpression::CaseInsensitiveOption); + static const auto validator = QRegularExpression("^[a-z0-9_.-]+(#(one|other))?$", QRegularExpression::CaseInsensitiveOption); if (!validator.match(key).hasMatch()) { return false; } @@ -78,6 +78,21 @@ QString PrepareCommandString(int index) { } // namespace +const std::array kPluralParts = { + "zero", + "one", + "two", + "few", + "many", + "other", +}; + +const QString kPluralTag = "count"; + +QString ComputePluralKey(const QString &base, int index) { + return base + "__plural" + QString::number(index); +} + ParsedFile::ParsedFile(const Options &options) : filePath_(options.inputPath) , file_(filePath_) @@ -104,7 +119,7 @@ bool ParsedFile::read() { logErrorUnexpectedToken() << "'=' for '" << keyToken.value.toStdString() << "' key"; } } else { - logErrorUnexpectedToken() << "string key name (/^[a-z0-9_.-]+$/i)"; + logErrorUnexpectedToken() << "string key name (/^[a-z0-9_.-]+(#(one|other))?$/i)"; } } if (file_.atEnd()) { @@ -113,9 +128,46 @@ bool ParsedFile::read() { logErrorUnexpectedToken() << "ansi string key name"; } while (!failed()); + fillPluralTags(); + return !failed(); } +void ParsedFile::fillPluralTags() { + auto count = result_.entries.size(); + for (auto i = 0; i != count;) { + auto &baseEntry = result_.entries[i]; + if (baseEntry.keyBase.isEmpty()) { + ++i; + continue; + } + logAssert(i + kPluralPartCount < count); + + // Accumulate all tags from all plural variants. + auto tags = std::vector(); + for (auto j = i; j != i + kPluralPartCount; ++j) { + if (tags.empty()) { + tags = result_.entries[j].tags; + } else { + for (auto &tag : result_.entries[j].tags) { + if (std::find(tags.begin(), tags.end(), tag) == tags.end()) { + tags.push_back(tag); + } + } + } + } + logAssert(!tags.empty()); + logAssert(tags.front().tag == kPluralTag); + + // Set this tags list to all plural variants. + for (auto j = i; j != i + kPluralPartCount; ++j) { + result_.entries[j].tags = tags; + } + + i += kPluralPartCount; + } +} + BasicToken ParsedFile::assertNextToken(BasicToken::Type type) { auto result = file_.getToken(type); if (!result) { @@ -204,24 +256,85 @@ QString ParsedFile::extractTagData(const QString &tagText, LangPack *to) { return PrepareCommandString(tagIndex); } -void ParsedFile::addEntity(const QString &key, const QString &value) { - for (auto &entry : result_.entries) { - if (entry.key == key) { - logError(kErrorBadString) << "duplicate found for key '" << key.toStdString() << "'"; +void ParsedFile::addEntity(QString key, const QString &value) { + auto pluralPartOffset = key.indexOf('#'); + auto pluralIndex = -1; + if (pluralPartOffset >= 0) { + auto pluralPart = key.mid(pluralPartOffset + 1); + pluralIndex = std::find(kPluralParts.begin(), kPluralParts.end(), pluralPart) - kPluralParts.begin(); + if (pluralIndex < 0 || pluralIndex >= kPluralParts.size()) { + logError(kErrorBadString) << "bad plural part for key '" << key.toStdString() << "': '" << pluralPart.toStdString() << "'"; return; } + key = key.mid(0, pluralPartOffset); + } + auto checkKey = [this](const QString &key) { + for (auto &entry : result_.entries) { + if (entry.key == key) { + if (entry.keyBase.isEmpty() || !entry.tags.empty()) { + // Empty tags in plural entry means it was not encountered yet. + logError(kErrorBadString) << "duplicate found for key '" << key.toStdString() << "'"; + return false; + } + } + } + return true; + }; + if (!checkKey(key)) { + return; } auto tagsData = LangPack(); auto entry = LangPack::Entry(); entry.key = key; entry.value = extractTagsData(value, &tagsData); entry.tags = tagsData.tags; - result_.entries.push_back(entry); - for (auto &pluralEntry : tagsData.entries) { - auto taggedEntry = LangPack::Entry(); - taggedEntry.key = key + "__" + pluralEntry.key; - taggedEntry.value = pluralEntry.value; - result_.entries.push_back(taggedEntry); + if (pluralIndex >= 0) { + logAssert(tagsData.entries.empty()); + + entry.keyBase = entry.key; + entry.key = ComputePluralKey(entry.keyBase, pluralIndex); + if (!checkKey(entry.key)) { + return; + } + auto baseIndex = -1; + auto alreadyCount = result_.entries.size(); + for (auto i = 0; i != alreadyCount; ++i) { + if (result_.entries[i].keyBase == entry.keyBase) { + // This is not the first appearance of this plural key. + baseIndex = i; + break; + } + } + if (baseIndex < 0) { + baseIndex = result_.entries.size(); + for (auto i = 0; i != kPluralPartCount; ++i) { + auto addingEntry = LangPack::Entry(); + addingEntry.keyBase = entry.keyBase; + addingEntry.key = ComputePluralKey(entry.keyBase, i); + result_.entries.push_back(addingEntry); + } + } + auto entryIndex = baseIndex + pluralIndex; + logAssert(entryIndex < result_.entries.size()); + auto &realEntry = result_.entries[entryIndex]; + logAssert(realEntry.key == entry.key); + realEntry.value = entry.value; + + // Add all new tags to the existing ones. + realEntry.tags = std::vector(1, LangPack::Tag { kPluralTag }); + for (auto &tag : entry.tags) { + if (std::find(realEntry.tags.begin(), realEntry.tags.end(), tag) == realEntry.tags.end()) { + realEntry.tags.push_back(tag); + } + } + } else { + result_.entries.push_back(entry); + for (auto &pluralEntry : tagsData.entries) { + auto taggedEntry = LangPack::Entry(); + taggedEntry.key = key + "__" + pluralEntry.key; + taggedEntry.value = pluralEntry.value; + result_.entries.push_back(taggedEntry); + } } } diff --git a/Telegram/SourceFiles/codegen/lang/parsed_file.h b/Telegram/SourceFiles/codegen/lang/parsed_file.h index ae5431f59..a638d34c1 100644 --- a/Telegram/SourceFiles/codegen/lang/parsed_file.h +++ b/Telegram/SourceFiles/codegen/lang/parsed_file.h @@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include #include +#include #include #include #include "codegen/common/basic_tokenized_file.h" @@ -30,6 +31,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace codegen { namespace lang { +constexpr auto kPluralPartCount = 6; +extern const std::array kPluralParts; +extern const QString kPluralTag; +QString ComputePluralKey(const QString &base, int index); + struct LangPack { struct Tag { QString tag; @@ -37,6 +43,7 @@ struct LangPack { struct Entry { QString key; QString value; + QString keyBase; // Empty for not plural entries. std::vector tags; }; std::vector entries; @@ -44,6 +51,14 @@ struct LangPack { }; +inline bool operator==(const LangPack::Tag &a, const LangPack::Tag &b) { + return a.tag == b.tag; +} + +inline bool operator!=(const LangPack::Tag &a, const LangPack::Tag &b) { + return !(a == b); +} + // Parses an input file to the internal struct. class ParsedFile { public: @@ -83,10 +98,12 @@ private: using BasicToken = common::BasicTokenizedFile::Token; BasicToken assertNextToken(BasicToken::Type type); - void addEntity(const QString &key, const QString &value); + void addEntity(QString key, const QString &value); QString extractTagsData(const QString &value, LangPack *to); QString extractTagData(const QString &tag, LangPack *to); + void fillPluralTags(); + QString filePath_; common::BasicTokenizedFile file_; Options options_; diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 888f717d5..f291ea806 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -326,7 +326,7 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO } if (_state == SearchedState || !_searchResults.empty()) { - QString text = lng_search_found_results(lt_count, _searchResults.empty() ? 0 : (_searchedMigratedCount + _searchedCount)); + auto text = _searchResults.empty() ? lang(lng_search_no_results) : lng_search_found_results(lt_count, _searchedMigratedCount + _searchedCount); p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg); if (!paintingOther) { p.setFont(st::searchedBarFont); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index bd7f4a0c6..dcdbb168c 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -2158,9 +2158,9 @@ HistoryService::PreparedText HistoryService::prepareGameScoreText() { result.links.push_back(fromLink()); auto gameTitle = computeGameTitle(); if (gameTitle.isEmpty()) { - result.text = lng_action_game_score_no_game(lt_from, fromLinkText(), lt_count, scoreNumber); + result.text = lng_action_game_score_no_game(lt_count, scoreNumber, lt_from, fromLinkText()); } else { - result.text = lng_action_game_score(lt_from, fromLinkText(), lt_count, scoreNumber, lt_game, gameTitle); + result.text = lng_action_game_score(lt_count, scoreNumber, lt_from, fromLinkText(), lt_game, gameTitle); } } return result; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 31322f3e0..d6281872e 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -4031,24 +4031,34 @@ void HistoryWidget::updateOnlineDisplay() { text = App::onlineText(user, t); titlePeerTextOnline = App::onlineColorUse(user, t); } else if (_peer->isChat()) { - ChatData *chat = _peer->asChat(); + auto chat = _peer->asChat(); if (!chat->amIn()) { text = lang(lng_chat_status_unaccessible); } else if (chat->participants.isEmpty()) { - text = _titlePeerText.isEmpty() ? lng_chat_status_members(lt_count, chat->count < 0 ? 0 : chat->count) : _titlePeerText; + if (!_titlePeerText.isEmpty()) { + text = _titlePeerText; + } else if (chat->count <= 0) { + text = lang(lng_group_status); + } else { + text = lng_chat_status_members(lt_count, chat->count); + } } else { - int32 onlineCount = 0; - bool onlyMe = true; - for (ChatData::Participants::const_iterator i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) { + auto online = 0; + auto onlyMe = true; + for (auto i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) { if (i.key()->onlineTill > t) { - ++onlineCount; + ++online; if (onlyMe && i.key() != App::self()) onlyMe = false; } } - if (onlineCount && !onlyMe) { - text = lng_chat_status_members_online(lt_count, chat->participants.size(), lt_count_online, onlineCount); - } else { + if (online > 0 && !onlyMe) { + auto membersCount = lng_chat_status_members(lt_count, chat->participants.size()); + auto onlineCount = lng_chat_status_online(lt_count, online); + text = lng_chat_status_members_online(lt_members_count, membersCount, lt_online_count, onlineCount); + } else if (chat->participants.size() > 0) { text = lng_chat_status_members(lt_count, chat->participants.size()); + } else { + text = lang(lng_group_status); } } } else if (_peer->isChannel()) { @@ -4056,21 +4066,27 @@ void HistoryWidget::updateOnlineDisplay() { if (_peer->asChannel()->mgInfo->lastParticipants.size() < _peer->asChannel()->membersCount() || _peer->asChannel()->lastParticipantsCountOutdated()) { if (App::api()) App::api()->requestLastParticipants(_peer->asChannel()); } - int32 onlineCount = 0; + auto online = 0; bool onlyMe = true; for (auto i = _peer->asChannel()->mgInfo->lastParticipants.cbegin(), e = _peer->asChannel()->mgInfo->lastParticipants.cend(); i != e; ++i) { if ((*i)->onlineTill > t) { - ++onlineCount; + ++online; if (onlyMe && (*i) != App::self()) onlyMe = false; } } - if (onlineCount && !onlyMe) { - text = lng_chat_status_members_online(lt_count, _peer->asChannel()->membersCount(), lt_count_online, onlineCount); - } else { + if (online && !onlyMe) { + auto membersCount = lng_chat_status_members(lt_count, _peer->asChannel()->membersCount()); + auto onlineCount = lng_chat_status_online(lt_count, online); + text = lng_chat_status_members_online(lt_members_count, membersCount, lt_online_count, onlineCount); + } else if (_peer->asChannel()->membersCount() > 0) { text = lng_chat_status_members(lt_count, _peer->asChannel()->membersCount()); + } else { + text = lang(lng_group_status); } + } else if (_peer->asChannel()->membersCount() > 0) { + text = lng_chat_status_members(lt_count, _peer->asChannel()->membersCount()); } else { - text = _peer->asChannel()->membersCount() ? lng_chat_status_members(lt_count, _peer->asChannel()->membersCount()) : lang(_peer->isMegagroup() ? lng_group_status : lng_channel_status); + text = lang(_peer->isMegagroup() ? lng_group_status : lng_channel_status); } } if (_titlePeerText != text) { diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index dd6ed678b..e5009a2d4 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -256,17 +256,18 @@ void Widget::resetAccount() { auto type = error.type(); if (type.startsWith(qstr("2FA_CONFIRM_WAIT_"))) { - int seconds = type.mid(qstr("2FA_CONFIRM_WAIT_").size()).toInt(); - int days = (seconds + 59) / 86400; - int hours = ((seconds + 59) % 86400) / 3600; - int minutes = ((seconds + 59) % 3600) / 60; - QString when; + auto seconds = type.mid(qstr("2FA_CONFIRM_WAIT_").size()).toInt(); + auto days = (seconds + 59) / 86400; + auto hours = ((seconds + 59) % 86400) / 3600; + auto minutes = ((seconds + 59) % 3600) / 60; + auto when = lng_signin_reset_minutes(lt_count, minutes); if (days > 0) { - when = lng_signin_reset_in_days(lt_count_days, days, lt_count_hours, hours, lt_count_minutes, minutes); + auto daysCount = lng_signin_reset_days(lt_count, days); + auto hoursCount = lng_signin_reset_hours(lt_count, hours); + when = lng_signin_reset_in_days(lt_days_count, daysCount, lt_hours_count, hoursCount, lt_minutes_count, when); } else if (hours > 0) { - when = lng_signin_reset_in_hours(lt_count_hours, hours, lt_count_minutes, minutes); - } else { - when = lng_signin_reset_in_minutes(lt_count_minutes, minutes); + auto hoursCount = lng_signin_reset_hours(lt_count, hours); + when = lng_signin_reset_in_hours(lt_hours_count, hoursCount, lt_minutes_count, when); } Ui::show(Box(lng_signin_reset_wait(lt_phone_number, App::formatPhone(getData()->phone), lt_when, when))); } else if (type == qstr("2FA_RECENT_CONFIRM")) { diff --git a/Telegram/SourceFiles/lang/lang_cloud_manager.cpp b/Telegram/SourceFiles/lang/lang_cloud_manager.cpp index 6c7639712..74b456d87 100644 --- a/Telegram/SourceFiles/lang/lang_cloud_manager.cpp +++ b/Telegram/SourceFiles/lang/lang_cloud_manager.cpp @@ -196,7 +196,7 @@ void CloudManager::switchToLanguage(QString id) { if (id.isEmpty()) { id = DefaultLanguageId(); } - if (_langpack.id() == id) { + if (_langpack.id() == id && id != qstr("custom")) { return; } diff --git a/Telegram/SourceFiles/lang/lang_file_parser.cpp b/Telegram/SourceFiles/lang/lang_file_parser.cpp index 9739bf696..c5e24e4b9 100644 --- a/Telegram/SourceFiles/lang/lang_file_parser.cpp +++ b/Telegram/SourceFiles/lang/lang_file_parser.cpp @@ -78,7 +78,7 @@ bool FileParser::readKeyValue(const char *&from, const char *end) { } ++from; const char *nameStart = from; - while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) { + while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9') || *from == '#')) { ++from; } diff --git a/Telegram/SourceFiles/lang/lang_instance.cpp b/Telegram/SourceFiles/lang/lang_instance.cpp index 49c8e8955..d18b269f8 100644 --- a/Telegram/SourceFiles/lang/lang_instance.cpp +++ b/Telegram/SourceFiles/lang/lang_instance.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "platform/platform_specific.h" #include "boxes/confirm_box.h" #include "lang/lang_file_parser.h" +#include "base/qthelp_regex.h" namespace Lang { namespace { @@ -43,6 +44,10 @@ constexpr str_const kLegacyLanguages[] = { "ko", }; +QString ConvertLegacyLanguageId(const QString &languageId) { + return languageId.toLower().replace('_', '-'); +} + class ValueParser { public: ValueParser(const QByteArray &key, LangKey keyIndex, const QByteArray &value); @@ -51,17 +56,11 @@ public: Expects(!_failed); return std::move(_result); } - std::map takePluralValues() { - Expects(!_failed); - return std::move(_plural); - } bool parse(); private: void appendToResult(const char *nextBegin); - void appendToPlural(const char *nextBegin); - bool feedPluralValue(); bool logError(const QString &text); bool readTag(); @@ -72,16 +71,11 @@ private: ushort _currentTagIndex = 0; QString _currentTagReplacer; - QString _pluralValue; - int _pluralIndex = 0; - bool _pluralNumericFound = false; - bool _failed = true; const char *_begin, *_ch, *_end; QString _result; - std::map _plural; OrderedSet _tagsUsed; }; @@ -99,29 +93,6 @@ void ValueParser::appendToResult(const char *nextBegin) { _begin = nextBegin; } -void ValueParser::appendToPlural(const char *nextBegin) { - if (_ch > _begin) _pluralValue.append(QString::fromUtf8(_begin, _ch - _begin)); - _begin = nextBegin; -} - -bool ValueParser::feedPluralValue() { - appendToPlural(_ch + 1); - - if (_pluralIndex >= kTagsPluralVariants) { - return logError("Too many values inside counted tag"); - } - auto pluralKeyIndex = GetSubkeyIndex(_keyIndex, _currentTagIndex, _pluralIndex); - if (pluralKeyIndex == kLangKeysCount) { - return logError("Unexpected counted tag"); - } else { - _plural.emplace(pluralKeyIndex, _pluralValue); - } - ++_pluralIndex; - _pluralValue = QString(); - _pluralNumericFound = false; - return true; -}; - bool ValueParser::logError(const QString &text) { _failed = true; auto loggedKey = (_currentTag.size() > 0) ? (_key + QString(':') + _currentTag) : QString(_key); @@ -149,8 +120,8 @@ bool ValueParser::readTag() { } _currentTag = QLatin1String(tagStart, _ch - tagStart); - if (_ch == _end || (*_ch != '}' && *_ch != ':')) { - return logError("Expected '}' or ':' after tag name"); + if (_ch == _end || *_ch != '}') { + return logError("Expected '}' after tag name"); } _currentTagIndex = GetTagIndex(_currentTag); @@ -189,39 +160,6 @@ bool ValueParser::parse() { _result.append(_currentTagReplacer); _begin = _ch + 1; - if (*_ch == ':') { - _pluralIndex = 0; - while (_ch != _end && *_ch != '}') { - if (*_ch == '|') { - if (!feedPluralValue()) { - return false; - } - } else if (*_ch == '\\') { - if (_ch + 1 >= _end) { - return logError("Unexpected end of file inside counted tag"); - } - if (*(_ch + 1) == '{' || *(_ch + 1) == '#' || *(_ch + 1) == '}') { - appendToPlural(_ch + 1); - } - } else if (*_ch == '{') { - return logError("Unexpected tag inside counted tag"); - } else if (*_ch == '#') { - if (_pluralNumericFound) { - return logError("Replacement '#' double used inside counted tag"); - } - _pluralNumericFound = true; - appendToPlural(_ch + 1); - _pluralValue.append(_currentTagReplacer); - } - ++_ch; - } - if (_ch == _end) { - return logError("Unexpected end of value inside counted tag"); - } - if (!feedPluralValue()) { - return false; - } - } _currentTag = QLatin1String(); } } @@ -259,6 +197,7 @@ void Instance::switchToId(const QString &id) { } _updated.notify(); } + updatePluralRules(); } void Instance::switchToCustomFile(const QString &filePath) { @@ -271,6 +210,7 @@ void Instance::switchToCustomFile(const QString &filePath) { void Instance::reset() { _values.clear(); _nonDefaultValues.clear(); + _nonDefaultSet.clear(); _legacyId = kLegacyLanguageNone; _customFilePathAbsolute = QString(); _customFilePathRelative = QString(); @@ -286,6 +226,7 @@ void Instance::fillDefaults() { for (auto i = 0; i != kLangKeysCount; ++i) { _values.emplace_back(GetOriginalValue(LangKey(i))); } + _nonDefaultSet = std::vector(kLangKeysCount, 0); } QString Instance::systemLangCode() const { @@ -388,6 +329,7 @@ void Instance::fillFromSerialized(const QByteArray &data) { for (auto i = 0, count = nonDefaultValuesCount * 2; i != count; i += 2) { applyValue(nonDefaultStrings[i], nonDefaultStrings[i + 1]); } + updatePluralRules(); } void Instance::loadFromContent(const QByteArray &content) { @@ -416,6 +358,7 @@ void Instance::fillFromCustomFile(const QString &filePath) { auto content = Lang::FileParser::ReadFile(absolutePath, relativePath); if (!content.isEmpty()) { loadFromCustomContent(absolutePath, relativePath, content); + updatePluralRules(); } } @@ -441,6 +384,8 @@ void Instance::fillFromLegacy(int legacyId, const QString &legacyPath) { loadFromContent(content); } } + _id = ConvertLegacyLanguageId(_id); + updatePluralRules(); } template @@ -508,25 +453,41 @@ std::map Instance::ParseStrings(const MTPVector -void Instance::ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result) { +LangKey Instance::ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result) { auto keyIndex = GetKeyIndex(QLatin1String(key)); if (keyIndex == kLangKeysCount) { LOG(("Lang Error: Unknown key '%1'").arg(QString::fromLatin1(key))); - return; + return kLangKeysCount; } ValueParser parser(key, keyIndex, value); if (parser.parse()) { result[keyIndex] = parser.takeResult(); - for (auto &plural : parser.takePluralValues()) { - result[plural.first] = plural.second; - } + return keyIndex; } + return kLangKeysCount; } void Instance::applyValue(const QByteArray &key, const QByteArray &value) { _nonDefaultValues[key] = value; - ParseKeyValue(key, value, _values); + auto index = ParseKeyValue(key, value, _values); + if (index != kLangKeysCount) { + _nonDefaultSet[index] = 1; + } +} + +void Instance::updatePluralRules() { + auto id = _id; + if (isCustom()) { + auto path = _customFilePathAbsolute.isEmpty() ? _customFilePathRelative : _customFilePathAbsolute; + auto name = QFileInfo(path).fileName(); + if (auto match = qthelp::regex_match("_([a-z]{2,3})(_[A-Z]{2,3}|\\-[a-z]{2,3})?\\.", name)) { + id = match->captured(1); + } + } else if (auto match = qthelp::regex_match("^([a-z]{2,3})(_[A-Z]{2,3}|\\-[a-z]{2,3})$", id)) { + id = match->captured(1); + } + UpdatePluralRules(id); } void Instance::resetValue(const QByteArray &key) { diff --git a/Telegram/SourceFiles/lang/lang_instance.h b/Telegram/SourceFiles/lang/lang_instance.h index 91277e717..581f870c9 100644 --- a/Telegram/SourceFiles/lang/lang_instance.h +++ b/Telegram/SourceFiles/lang/lang_instance.h @@ -75,6 +75,11 @@ public: Expects(_values.size() == kLangKeysCount); return _values[key]; } + bool isNonDefaultPlural(LangKey key) { + Expects(key >= 0 && key < kLangKeysCount); + Expects(_nonDefaultSet.size() == kLangKeysCount); + return _nonDefaultSet[key] || _nonDefaultSet[key + 1] || _nonDefaultSet[key + 2] || _nonDefaultSet[key + 3] || _nonDefaultSet[key + 4] || _nonDefaultSet[key + 5]; + } private: // SetCallback takes two QByteArrays: key, value. @@ -85,7 +90,7 @@ private: // Writes each key-value pair in the result container. template - static void ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result); + static LangKey ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result); void applyValue(const QByteArray &key, const QByteArray &value); void resetValue(const QByteArray &key); @@ -94,6 +99,7 @@ private: void fillFromCustomFile(const QString &filePath); void loadFromContent(const QByteArray &content); void loadFromCustomContent(const QString &absolutePath, const QString &relativePath, const QByteArray &content); + void updatePluralRules(); QString _id; int _legacyId = kLegacyLanguageNone; @@ -106,6 +112,7 @@ private: mutable QString _systemLanguage; std::vector _values; + std::vector _nonDefaultSet; std::map _nonDefaultValues; }; diff --git a/Telegram/SourceFiles/lang/lang_tag.cpp b/Telegram/SourceFiles/lang/lang_tag.cpp index 1790a0057..56bdbfb93 100644 --- a/Telegram/SourceFiles/lang/lang_tag.cpp +++ b/Telegram/SourceFiles/lang/lang_tag.cpp @@ -23,6 +23,111 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "lang/lang_keys.h" namespace Lang { +namespace { + +// +// http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html +// + +constexpr auto kShiftZero = ushort(0); +constexpr auto kShiftOne = ushort(1); +constexpr auto kShiftTwo = ushort(2); +constexpr auto kShiftFew = ushort(3); +constexpr auto kShiftMany = ushort(4); +constexpr auto kShiftOther = ushort(5); + +// +// n absolute value of the source number (integer and decimals). +// i integer digits of n. +// v number of visible fraction digits in n, with trailing zeros. +// w number of visible fraction digits in n, without trailing zeros. +// f visible fractional digits in n, with trailing zeros. +// t visible fractional digits in n, without trailing zeros. +// +// Let n be int, being -1 for non-integer numbers and n == i for integer numbers. +// It is fine while in the rules we compare n only to integers. +// +// -123.450: n = -1, i = 123, v = 3, w = 2, f = 450, t = 45 +// + +using ChoosePluralMethod = ushort (*)(int n, int i, int v, int w, int f, int t); + +ushort ChoosePluralAr(int n, int i, int v, int w, int f, int t) { + if (n == 0) { + return kShiftZero; + } else if (n == 1) { + return kShiftOne; + } else if (n == 2) { + return kShiftTwo; + } else if (n < 0) { + return kShiftOther; + } + auto mod100 = (n % 100); + if (mod100 >= 3 && mod100 <= 10) { + return kShiftFew; + } else if (mod100 >= 11 && mod100 <= 99) { + return kShiftMany; + } + return kShiftOther; +} + +ushort ChoosePluralEn(int n, int i, int v, int w, int f, int t) { + if (i == 1 && v == 0) { + return kShiftOne; + } + return kShiftOther; +} + +ushort ChoosePluralPt(int n, int i, int v, int w, int f, int t) { + if (i == 0 || i == 1) { + return kShiftOne; + } + return kShiftOther; +} + +ushort ChoosePluralEs(int n, int i, int v, int w, int f, int t) { + if (n == 1) { + return kShiftOne; + } + return kShiftOther; +} + +ushort ChoosePluralKo(int n, int i, int v, int w, int f, int t) { + return kShiftOther; +} + +ushort ChoosePluralRu(int n, int i, int v, int w, int f, int t) { + if (v == 0) { + auto mod10 = (i % 10); + auto mod100 = (i % 100); + if ((mod10 == 1) && (mod100 != 11)) { + return kShiftOne; + } else if ((mod10 >= 2) && (mod10 <= 4) && (mod100 < 12 || mod100 > 14)) { + return kShiftFew; + } else { + return kShiftMany; + } + } + return kShiftMany;// kShiftOther; +} + +QMap GeneratePluralRulesMap() { + auto result = QMap(); + result.insert(qsl("ar"), ChoosePluralAr); +// result.insert(qsl("de"), ChoosePluralEn); +// result.insert(qsl("en"), ChoosePluralEn); // En is default, so we don't fill it inside the map. + result.insert(qsl("es"), ChoosePluralEs); +// result.insert(qsl("it"), ChoosePluralEn); + result.insert(qsl("ko"), ChoosePluralKo); +// result.insert(qsl("nl"), ChoosePluralEn); + result.insert(qsl("pt"), ChoosePluralPt); + result.insert(qsl("ru"), ChoosePluralRu); + return result; +} + +ChoosePluralMethod ChoosePlural = ChoosePluralEn; + +} // namespace QString Tag(const QString &original, ushort tag, const QString &replacement) { for (auto s = original.constData(), ch = s, e = ch + original.size(); ch != e;) { @@ -53,29 +158,29 @@ QString Tag(const QString &original, ushort tag, const QString &replacement) { return original; } -QString Plural(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); +QString Plural(ushort keyBase, ushort tag, float64 value) { + // Simplified. + auto n = qAbs(value); + auto i = qFloor(n); + auto integer = (qCeil(n) == i); + auto v = integer ? 0 : 6; + auto w = v; + auto f = integer ? 0 : 111111; + auto t = integer ? 0 : 111111; + + auto &langpack = Lang::Current(); + auto useNonDefaultPlural = (ChoosePlural != ChoosePluralEn && langpack.isNonDefaultPlural(LangKey(keyBase))); + auto shift = (useNonDefaultPlural ? ChoosePlural : ChoosePluralEn)((integer ? i : -1), i, v, w, f, t); + auto string = langpack.getValue(LangKey(keyBase + shift)); + if (i == qCeil(n)) { + return Tag(string, tag, QString::number(value)); } - while (key > key0) { - auto v = lang(LangKey(key)); - if (!v.isEmpty()) { - return Tag(v, tag, sv); - } - --key; - } - return Tag(lang(LangKey(key0)), tag, sv); + return Tag(string, tag, QString::number(qRound(value))); +} + +void UpdatePluralRules(const QString &languageId) { + static auto kMap = GeneratePluralRulesMap(); + ChoosePlural = kMap.value(languageId.toLower(), ChoosePluralEn); } } // namespace Lang diff --git a/Telegram/SourceFiles/lang/lang_tag.h b/Telegram/SourceFiles/lang/lang_tag.h index 218746243..360f67de8 100644 --- a/Telegram/SourceFiles/lang/lang_tag.h +++ b/Telegram/SourceFiles/lang/lang_tag.h @@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Lang { QString Tag(const QString &original, ushort tag, const QString &replacement); -QString Plural(ushort key0, ushort tag, float64 value); +QString Plural(ushort keyBase, ushort tag, float64 value); +void UpdatePluralRules(const QString &languageId); } // namespace Lang diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index acc76bb2d..adef15a06 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -130,8 +130,10 @@ QString formatDurationText(qint64 duration) { QString formatDurationWords(qint64 duration) { if (duration > 59) { auto minutes = (duration / 60); + auto minutesCount = lng_duration_minsec_minutes(lt_count, minutes); auto seconds = (duration % 60); - return lng_duration_minutes_seconds(lt_count_minutes, minutes, lt_count_seconds, seconds); + auto secondsCount = lng_duration_minsec_seconds(lt_count, seconds); + return lng_duration_minutes_seconds(lt_minutes_count, minutesCount, lt_seconds_count, secondsCount); } return lng_duration_seconds(lt_count, duration); } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index f099f6a3c..2232601e4 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -617,7 +617,7 @@ void MainWidget::updateForwardingTexts() { version += from->nameVersion; } if (fromUsers.size() > 2) { - from = lng_forwarding_from(lt_user, fromUsers.at(0)->shortName(), lt_count, fromUsers.size() - 1); + from = lng_forwarding_from(lt_count, fromUsers.size() - 1, lt_user, fromUsers.at(0)->shortName()); } else if (fromUsers.size() < 2) { from = fromUsers.at(0)->name; } else { @@ -4307,7 +4307,7 @@ bool MainWidget::usernameResolveFail(QString name, const RPCError &error) { void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) { switch (invite.type()) { case mtpc_chatInvite: { - auto &d(invite.c_chatInvite()); + auto &d = invite.c_chatInvite(); QVector participants; if (d.has_participants()) { @@ -4320,7 +4320,8 @@ void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) { } } _inviteHash = hash; - Ui::show(Box(qs(d.vtitle), d.vphoto, d.vparticipants_count.v, participants)); + auto isChannel = d.is_channel() && !d.is_megagroup(); + Ui::show(Box(qs(d.vtitle), isChannel, d.vphoto, d.vparticipants_count.v, participants)); } break; case mtpc_chatInviteAlready: { diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index b55a0bbe7..da8868243 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -810,7 +810,7 @@ void OverviewInner::paintEvent(QPaintEvent *e) { } else if (_inSearch && _searchResults.isEmpty() && _searchFull && (!_migrated || _searchFullMigrated) && !_searchTimer.isActive()) { p.setFont(st::noContactsFont->f); p.setPen(st::noContactsColor->p); - p.drawText(QRect(_rowsLeft, _marginTop, _rowWidth, _marginTop), lng_search_found_results(lt_count, 0), style::al_center); + p.drawText(QRect(_rowsLeft, _marginTop, _rowWidth, _marginTop), lang(lng_search_no_results), style::al_center); return; } diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index ee10cd27f..72b7fd968 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -373,23 +373,29 @@ void CoverWidget::refreshStatusText() { } _cancelPhotoUpload.destroy(); - int currentTime = unixtime(); + auto currentTime = unixtime(); if (_peerUser) { _statusText = App::onlineText(_peerUser, currentTime, true); _statusTextIsOnline = App::onlineColorUse(_peerUser, currentTime); } else if (_peerChat && _peerChat->amIn()) { - int fullCount = qMax(_peerChat->count, _peerChat->participants.size()); + auto fullCount = qMax(_peerChat->count, _peerChat->participants.size()); if (_onlineCount > 0 && _onlineCount <= fullCount) { - _statusText = lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _onlineCount); - } else { + auto membersCount = lng_chat_status_members(lt_count, fullCount); + auto onlineCount = lng_chat_status_online(lt_count, _onlineCount); + _statusText = lng_chat_status_members_online(lt_members_count, membersCount, lt_online_count, onlineCount); + } else if (_peerChat->count > 0) { _statusText = lng_chat_status_members(lt_count, _peerChat->count); + } else { + _statusText = lang(lng_group_status); } } else if (_peerChannel) { - int fullCount = _peerChannel->membersCount(); + auto fullCount = _peerChannel->membersCount(); if (_onlineCount > 0 && _onlineCount <= fullCount) { - _statusText = lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _onlineCount); - } else if (fullCount > 0 ) { - _statusText = lng_chat_status_members(lt_count, _peerChannel->membersCount()); + auto membersCount = lng_chat_status_members(lt_count, fullCount); + auto onlineCount = lng_chat_status_online(lt_count, _onlineCount); + _statusText = lng_chat_status_members_online(lt_members_count, membersCount, lt_online_count, onlineCount); + } else if (fullCount > 0) { + _statusText = lng_chat_status_members(lt_count, fullCount); } else { _statusText = lang(_peerChannel->isMegagroup() ? lng_group_status : lng_channel_status); } diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index a9c7c1308..2164df752 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -236,8 +236,8 @@ QString LastSeenPrivacyController::warning() { QString LastSeenPrivacyController::exceptionLinkText(Exception exception, int count) { switch (exception) { - case Exception::Always: return lng_edit_privacy_lastseen_always(lt_count, count); - case Exception::Never: return lng_edit_privacy_lastseen_never(lt_count, count); + case Exception::Always: return (count > 0) ? lng_edit_privacy_lastseen_always(lt_count, count) : lang(lng_edit_privacy_lastseen_always_empty); + case Exception::Never: return (count > 0) ? lng_edit_privacy_lastseen_never(lt_count, count) : lang(lng_edit_privacy_lastseen_never_empty); } Unexpected("Invalid exception value."); } @@ -290,8 +290,8 @@ QString GroupsInvitePrivacyController::description() { QString GroupsInvitePrivacyController::exceptionLinkText(Exception exception, int count) { switch (exception) { - case Exception::Always: return lng_edit_privacy_groups_always(lt_count, count); - case Exception::Never: return lng_edit_privacy_groups_never(lt_count, count); + case Exception::Always: return (count > 0) ? lng_edit_privacy_groups_always(lt_count, count) : lang(lng_edit_privacy_groups_always_empty); + case Exception::Never: return (count > 0) ? lng_edit_privacy_groups_never(lt_count, count) : lang(lng_edit_privacy_groups_never_empty); } Unexpected("Invalid exception value."); } @@ -322,8 +322,8 @@ QString CallsPrivacyController::description() { QString CallsPrivacyController::exceptionLinkText(Exception exception, int count) { switch (exception) { - case Exception::Always: return lng_edit_privacy_calls_always(lt_count, count); - case Exception::Never: return lng_edit_privacy_calls_never(lt_count, count); + case Exception::Always: return (count > 0) ? lng_edit_privacy_calls_always(lt_count, count) : lang(lng_edit_privacy_calls_always_empty); + case Exception::Never: return (count > 0) ? lng_edit_privacy_calls_never(lt_count, count) : lang(lng_edit_privacy_calls_never_empty); } Unexpected("Invalid exception value."); } diff --git a/Telegram/gyp/codegen_rules.gypi b/Telegram/gyp/codegen_rules.gypi index b52a91bb9..13b1aced8 100644 --- a/Telegram/gyp/codegen_rules.gypi +++ b/Telegram/gyp/codegen_rules.gypi @@ -65,7 +65,7 @@ '<(PRODUCT_DIR)/codegen_style<(exe_ext)', '-I', '<(res_loc)', '-I', '<(src_loc)', '-o', '<(SHARED_INTERMEDIATE_DIR)/styles', - '-w', '<(PRODUCT_DIR)/../..', + '-w', '<(PRODUCT_DIR)/..', # GYP/Ninja bug workaround: if we specify just <(RULE_INPUT_PATH) # the <(RULE_INPUT_ROOT) variables won't be available in Ninja, @@ -90,7 +90,7 @@ 'action': [ '<(PRODUCT_DIR)/codegen_lang<(exe_ext)', '-o', '<(SHARED_INTERMEDIATE_DIR)', '<(res_loc)/langs/lang.strings', - '-w', '<(PRODUCT_DIR)/../..', + '-w', '<(PRODUCT_DIR)/..', ], 'message': 'codegen_lang-ing lang.strings..', 'process_outputs_as_sources': 1, @@ -107,7 +107,7 @@ 'action': [ '<(PRODUCT_DIR)/codegen_numbers<(exe_ext)', '-o', '<(SHARED_INTERMEDIATE_DIR)', '<(res_loc)/numbers.txt', - '-w', '<(PRODUCT_DIR)/../..', + '-w', '<(PRODUCT_DIR)/..', ], 'message': 'codegen_numbers-ing numbers.txt..', 'process_outputs_as_sources': 1, @@ -158,7 +158,7 @@ '<(PRODUCT_DIR)/codegen_style<(exe_ext)', '-I', '<(res_loc)', '-I', '<(src_loc)', '-o', '<(SHARED_INTERMEDIATE_DIR)/styles', - '-w', '<(PRODUCT_DIR)/../..', + '-w', '<(PRODUCT_DIR)/..', # GYP/Ninja bug workaround: if we specify just <(RULE_INPUT_PATH) # the <(RULE_INPUT_ROOT) variables won't be available in Ninja,