I'm trying to make my Xamarin Forms application support multiple language like English and Arabic which is based on user selection not based on phone language.
The following code shows how I change my language to Arabic or English.
The following code is a part of the TranslateExtension from the Xamarin Forms Samples:
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Text == null)
return "";
ResourceManager resmgr = new ResourceManager(ResourceId, typeof(TranslateExtension).GetTypeInfo().Assembly);
var translation = resmgr.GetString(Text, ci);
if (translation == null)
{
#if DEBUG
throw new ArgumentException(
String.Format("Key '{0}' was not found in resources '{1}' for culture '{2}'.", Text, ResourceId, ci.Name),"Text");
#else
translation = Text; // HACK: returns the key, which GETS DISPLAYED TO THE USER
#endif
}
return translation;
}
Can anyone help me and tell me how to make this work based on user selection not based on device language. Thanks for help.
Apparently the code you pasted came from this example. Based on that example, you can see there is a variable ci which contains the CultureInfo on which the extension works.
So the solution to your problem is setting that ci variable correctly. If the user selects a locale, you probably save that somewhere. In the TranslateExtension constructor, you can then retrieve that locale from where you saved it.
Something like:
public TranslateExtension() {
var userlocaleSvc = DependencyService.Get<IUserLocale>();
if (userlocaleSvc.CustomCultureInfoSet()) // this method is made up
{
ci = userlocaleSvc.GetCultureInfo(); // this method is made up
} else {
ci = DependencyService.Get<ILocalize>().GetCurrentCultureInfo(); // fallback
}
}
You can also set the CultureInfo on the Resource, so resources that don't use the TranslateExtension also get translated properly (AppResources is the name you give your resource file, you can use any name you want):
AppResources.Culture = new CultureInfo ("<shortcode of selected locale>");
Refer to the Xamarin documentation on localization here
Related
I need to localize DatePicker. From what I read the dialog uses system locale instead of current thread locale. Is there a workaround?
As of now, my application supports three languages user can choose from (ResourceManagers in the .NETStandard project). If the device uses different language, the whole application will be in English except for DatePicker which will be in the system language.
Both of the following are acceptable solutions:
User can choose which language they wish to use throughout the lifetime of the application
Application uses the system language if it's supported, English otherwise
Edit:
Custom renderer implementation as suggested by Raimo
public class LocaleAwareDatePickerRenderer : DatePickerRenderer{
public LocaleAwareDatePickerRenderer( Context context ) : base(context) { }
protected override EditText CreateNativeControl() {
return new EditText(Context) {TextLocale = new Locale("cs"), Focusable = false, Clickable = true, Tag = this};
}
}
Use a custom DatePickerRenderer and add these two lines to set your control's Locale:
Locale locale = new Locale(LocalizationService.GetCurrentThreadCultureInfo().TwoLetterISOLanguageName);
Control.TextLocale = locale;
I'm developing a Visual Studio extension in which one of the implemented commands needs to be available only when the active document is a text document (like e.g. the Visual Studio's "Toggle Bookmark" does). The problem is that I can't figure out how to tell when that's the case.
Right now I have a half working solution. In the package's Initialize method I subscribe to DTE's WindowActivated event, and then whenever a window is activated I check if the window DocumentData property is of type TextDocument:
protected override void Initialize()
{
base.Initialize();
var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
dte.Events.WindowEvents.WindowActivated += WindowEventsOnWindowActivated;
//More initialization here...
}
//This is checked from command's BeforeQueryStatus
public bool ActiveDocumentIsText { get; private set; } = false;
private void WindowEventsOnWindowActivated(Window gotFocus, Window lostFocus)
{
if (gotFocus.Kind != "Document")
return; //It's not a document (e.g. it's a tool window)
TextDocument textDoc = gotFocus.DocumentData as TextDocument;
ActiveDocumentIsText = textDoc != null;
}
The problem with this approach is that 1) Window.DocumentData is documented as ".NET Framework internal use only", and 2) this gives a false positive when a document that has both a code view and a design view (e.g. a .visxmanifest file) is open in design mode.
I have tried to use IVsTextManager.GetActiveView as well, but this is returning the last active text view opened - so if I open a .txt file and then a .png file, it returns data for the .txt file even if it's not the active document anymore.
So, how do I check if the active document is a text document, or the code view of a document that can have a designer... and if possible, not using "undocumented" classes/members?
UPDATE: I found a slightly better solution. Inside the window activated handler:
ActiveDocumentIsText = gotFocus.Document.Object("TextDocument") != null;
At least this one is properly documented, but I still have the problem of false positives with designers.
I finally got it. It's somewhat tricky, but it works and is 100% "legal". Here's the recipe:
1- Make the package class implement IVsRunningDocTableEvents. Make all the methods just return VSConstants.S_OK;
2- Add the following field and the following auxiliary method to the package class:
private IVsRunningDocumentTable runningDocumentTable;
private bool DocIsOpenInLogicalView(string path, Guid logicalView, out IVsWindowFrame windowFrame)
{
return VsShellUtilities.IsDocumentOpen(
this,
path,
VSConstants.LOGVIEWID_TextView,
out var dummyHierarchy2, out var dummyItemId2,
out windowFrame);
}
3- Add the following to the Initialize method of the package class:
runningDocumentTable = GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
runningDocumentTable.AdviseRunningDocTableEvents(this, out var dummyCookie);
4- Don't blink, here comes the magic! Implement the IVsRunningDocTableEvents.OnBeforeDocumentWindowShow method as follows:
public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
{
runningDocumentTable.GetDocumentInfo(docCookie,
out var dummyFlags, out var dummyReadLocks, out var dummyEditLocks,
out string path,
out var dummyHierarchy, out var dummyItemId, out var dummyData);
IVsWindowFrame windowFrameForTextView;
var docIsOpenInTextView =
DocIsOpenInLogicalView(path, VSConstants.LOGVIEWID_Code, out windowFrameForTextView) ||
DocIsOpenInLogicalView(path, VSConstants.LOGVIEWID_TextView, out windowFrameForTextView);
//Is the document open in the code/text view,
//AND the window for that view is the one that has been just activated?
ActiveDocumentIsText = docIsOpenInTextView && pFrame == logicalViewWindowFrame;
return VSConstants.S_OK;
}
I'm using MVCSiteMapProvider v4.6.22 and have a dynamic node provider for one of my controllers.
Something like:
public class ProviderDetailsNodeProvider : DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
foreach (var provider in providers)
{
var dn = new DynamicNode()
{
Title = provider.Name,
ParentKey = "ParentKey",
Key = $"provider_master_{provider.ID}",
CanonicalUrl = "/url/something"
};
dn.RouteValues.Add("myRouteParamName", "myRouteParamValue");
yield return dn;
}
}
}
Without setting the CanonicalKey or CanonicalUrl properties of the DynamicNode, I get the correct behaviour. However I now wish to have multiple URLs pointing at the same content so I need to utilise the Canonical URL features of MVCSiteMapProvider.
If I attempt to set the CanonicalUrl as in the above snippet, or the CanonicalKey (my preferred choice), then when I attempt to use the helper methods, such as:
#Html.MvcSiteMap().SiteMapPath()
I get a NullReferenceException - it's the #Html.MvcSiteMap() which returns null.
What am I doing incorrectly, why do I get this NullReferenceException just by setting these properties against my dynamic nodes?
I'm using the MvcSiteMapProvider.MVC5 package, in an MVC6 application. I can't see a newer version on Nuget.
MVC 6 is not yet supported, as per the issue on NuGet.
I'm developing my first app and I'm trying to make it multilanguage.
Using AppHub example and some other link I created my resource files, fixed binding strings on my components and set a settings page.
First problem I had was that menu items and appbar buttons couldn't use localization strings (project complained when launched) so I have:
TextBlocks and other components binded with localized strings
Appbar buttons and items localized manually with a procedure loading localized strings
Now that I have my settings page, one item user can change is language.
Well, correct CultureInfo is selected according to user selection and then I use
Thread.CurrentThread.CurrentUICulture = Settings.Language;
When I press back button and return to main page, appbar items are localized correctly, while everything else is not.
The only workaround (that I really don't like, it's just to understand) is this:
public MainPage()
{
Thread.CurrentThread.CurrentUICulture = Settings.Language;
InitializeComponent();
// Everything else I need here
}
so I have to set language before components are created to make it work.
What's wrong? Which is the correct way to make a page refresh after changing language using binded strings?
I did not put a lot of code because I used basically the one provided in the link, but if you need more info I will edit my question.
I finally found a solution to automatically update my application components reacting to language change.
A good tutorial can be found here; briefly you must find a way to notify your app that localized resource is changed.
public class LocalizedStrings : ViewModelBase
{
private static AppResources localizedresources = new AppResources();
public AppResources LocalizedResources
{
get { return localizedresources; }
}
public void UpdateLanguage()
{
localizedresources = new AppResources();
RaisePropertyChanged(() => LocalizedResources);
}
public static LocalizedStrings LocalizedStringsResource
{
get
{
return Application.Current.Resources["LocalizedStrings"]
as LocalizedStrings;
}
}
}
With this when user change language, you should simply run
LocalizedStrings.LocalizedStringsResource.UpdateLanguage();
and the job is done.
Im working on a MVVM Windows phone app that displays weather info.
When the app loads up it opens MainPage.xaml. It makes a call the the service to get weather info and binds that data to the UI. Both Fahrenheit and Celcius info are returned but only one is displayed.
On the setting page, the user can select to view the temp in either Fahrenheit or Celcius.
The user can change this setting at any time and its stored in IsolatedStorageSettings.
The issue Im having is this:
when the user navigates to the Settings page and changes their preference for either Fahrenheit or Celcius, this change is not reflected on the main page.
This issue started me thinking about this in a broader context. I can see this being an issue in ANY MVVM app where the display depends on some setting in IsolatedStorage. Any time any setting in the IsoStore is updated, how does the ViewModels know this? When I navigate back in the NavigationStack from the settings page back to MainPage how can I force a rebind of the page?
The data in my model hasnt changed, only the data that I want to display has changed.
Am I missing something simple here?
Thanks in advance.
Alex
Probably you have code like this:
public double DisplayTemperature
{
get { return (IsCelsium) ? Celsium : Fahrenheit; }
}
And IsCelsium is:
public double IsCelsium
{
get { return (bool)settings["IsCelsium"]; }
set { settings["IsCelsium"] = value; }
}
So you need to add NotifyPropertyChanged event to notify UI to get new values from DisplayTemperature property:
public double IsCelsium
{
get { return (bool)settings["IsCelsium"]; }
set
{
settings["IsCelsium"] = value;
NotifyPropertyChanged("DisplayTemperature");
}
}
Take a look at Caliburn Micro. You could implement something similar or use CM itself. When using CM I don't even think about this stuff, CM makes it so simple.
When your ViewModel inherits from Screen there are life-cycle events that fire that you can override. For example, OnInitialize fires the very first time the ViewModel is Activated and OnActivate fires every time the VM is activated. There's also OnViewAttached and OnViewLoaded.
These methods are the perfect place to put logic to populate or re-populate data.
CM also has some special built in features for allowing one to easily tombstone a single property or an entire object graph into Iso or phone state.
ok, so Ive come up with a solution. Before I get to it, let me provide some background. The app that Im working on uses both MVVM Light and WP7Contrib. That being the case, I am using Funq for DI and the MVVMLight Toolkit. After I posted my initial question, I gave the question a bit more thought. I remembered a video that I watched a while back from MIX2011 called Deep Dive MVVM with Laurent Bugnion
http://channel9.msdn.com/Events/MIX/MIX11/OPN03
In it, he talks about just this problem (view models not living at the same time) on Windows Phone. The part in question starts around the 19 minute mark.
Anyway, after I remembered that and realized that the ViewModel locator is exposed in App.xaml, this became a trivial problem to solve. When the user changes the Fahrenheit/Celcius option on the setting page, I simply get a reference to the MainViewModel via the ViewModelLocator and reset the collection that is bound to the UI thus causing the bindings to update.
public bool AddOrUpdateValue(string Key, Object value)
{
bool valueChanged = false;
// If the key exists
if (settings.Contains(Key))
{
// If the value has changed
if (settings[Key] != value)
{
// Store the new value
settings[Key] = value;
valueChanged = true;
}
}
// Otherwise create the key.
else
{
settings.Add(Key, value);
valueChanged = true;
}
return valueChanged;
}
public bool ImperialSetting
{
get
{
return GetValueOrDefault<bool>(ImperialSettingKeyName, ImperialSettingDefault);
}
set
{
if (AddOrUpdateValue(ImperialSettingKeyName, value))
{
Save();
RaisePropertyChanged("ImperialSettingText");
var vml = new ViewModelLocator();
vml.MainViewModel.Cities = (App.Current as App).Cities;
}
}
}
It was a mistake on my part not to realize that I could get access to the viewModel via the ViewModelLocator. Hopefully this post saves someone else the time I burned on this issue.