API Guide > Technical Notes > Internationalizing plug-ins >

# Example code

The following source code, taken from the closestPointOnCurve plug-in, demonstrates how it was modified to support localized user interface strings. Relevant changes have been italicized. The complete set of source code for this plug-in is available as part of the Maya Bonus Tools.

## closestPointOnCurvePlugin.cpp

When the plug-in is initialized, a call to MFnPlugin::registerUIStrings() is made to register the strings used by the C++ code, and to invoke the MEL command closestPointOnCurveInitStrings which registers MEL string resources and loads localized values. The closestPointOnCurveStrings.h header file was created to provide a single point of definition for the C++ string resources used by the plug-in.

    // File: closestPointOnCurveStrings.cpp
    // HEADER FILES:
    #include "closestPointOnCurveCmd.h"
    #include "closestPointOnCurveNode.h"
    #include "closestPointOnCurveStrings.h"
    #include <maya/MFnPlugin.h>

    // Register all strings used by the plugin C++ source
    static MStatus registerMStringResources(void)
    {

        MStringResource::registerString(kNoValidObject);
        MStringResource::registerString(kInvalidType);
        MStringResource::registerString(kNoQueryFlag);
        return MS::kSuccess;
    }

    // INITIALIZES THE PLUGIN BY REGISTERING COMMAND AND NODE:
    MStatus initializePlugin(MObject obj)
    {
        MStatus status;
        MFnPlugin plugin(obj, PLUGIN_COMPANY, "4.0", "Any");

        // Register string resources used in the code and scripts
        // This is done first, so the strings are available.
        status = plugin.registerUIStrings(registerMStringResources, "closestPointOnCurveInitStrings");
        if (!status)
        {
            status.perror("registerUIStrings");
            return status;
        }
        status = plugin.registerCommand("closestPointOnCurve",  closestPointOnCurveCommand::creator,  closestPointOnCurveCommand::newSyntax);
        if (!status)
        {
            status.perror("registerCommand");
            return status;
        }
        status = plugin.registerNode("closestPointOnCurve",  closestPointOnCurveNode::id, closestPointOnCurveNode::creator,  closestPointOnCurveNode::initialize);
        if (!status)
        {
            status.perror("registerNode");
            return status;
        }
        return status;
    }

## closestPointOnCurveStrings.h

This header file was added to define the string resources used in the plug-in's C++ modules. When the resources are accessed in more than one source file this type of approach is recommended for providing a common set of definitions for the MStringResourceId values.

    // File: closestPointOnCurveStrings.h
    // MAYA HEADER FILES:
    #include <maya/MStringResource.h>
    #include <maya/MStringResourceId.h>
    // MStringResourceIds contain plugin id, unique resource id for
    // each string and the default value for the string.
    #define kPluginId  "closestPointOnCurve"
    #define kNoValidObject MStringResourceId(kPluginId,"kNoValidObject", \ "A curve or its transform node must be specified as a command argument, or using your current selection.")
    #define kInValidType MSTringResourceId (kPluginID, "kInvalidType", \ "Object ^1s has invalid type. Only a curve or its transform can be specified.")
    #define kNoQueryFlag MStringResourceId (kPluginId, "kNoQueryFlag", \ "You must specify AT LEAST ONE queryable flag in query mode. Use the "help" command to list all available flags.")

## closestPointOnCurveCmd.cpp

This file previously used hard-coded strings to display error messages. The hard-coded strings were redefined as MStringResourceId objects in closestPointOnCurveStrings.h. To use the string resources, a call to MStringResource::getString() is made to retrieve the current value of the resource. This code also illustrates the use of the MString::format() method for inserting variable arguments into message strings. Using format for constructing message strings is recommended for internationalized code instead of string concatenation. Formatted strings allow positional arguments to be correctly placed in the correct context and position when the string is translated to another language.

    // FILE: closestPointOnCurveCmd.cpp
    // HEADER FILES:
    #include "closestPointOnCurveCmd.h"
    #include "closestTangentUAndDistance.h"
    #include "closestPointOnCurveStrings.h"
    // COMPUTING THE OUTPUT VALUES FOR THE CLOSEST POSITION, NORMAL, TANGENT,
    // PARAMETER-U AND DISTANCE, OR CREATING A "closestPointOnCurve" NODE:
    MStatus closestPointOnCurveCommand::redoIt()
    {
        // DOUBLE-CHECK TO MAKE SURE THERE'S A SPECIFIED OBJECT TO EVALUATE ON:
        if (sList.length() == 0)
        {
            MStatus stat;
            MString msg = MStringResource::getString(kNoValidObject, stat);
            displayError(msg);
            return MStatus::kFailure;
        }

        // RETRIEVE THE SPECIFIED OBJECT AS A DAGPATH:
        MDagPath curveDagPath;
        sList.getDagPath(0, curveDagPath);

        // CHECK FOR INVALID NODE-TYPE INPUT WHEN SPECIFIED/SELECTED
        // NODE IS *NOT* A "CURVE" NOR "CURVE TRANSFORM":
        if (!curveDagPath.node().hasFn(MFn::kNurbsCurve) &&
            !(curveDagPath.node().hasFn(MFn::kTransform)
            && curveDagPath.hasFn(MFn::kNurbsCurve)))
        {
            MStatus stat;
            MString msg;
            // Use format to place variable string into message
            MString msgFmt = MStringResource::getString(kInvalidType, stat);
            MStringArray selectionStrings;
            sList.getSelectionStrings(0, selectionStrings);
            msg.format(msgFmt, selectionStrings[0]);
            displayError(msg);
            return MStatus::kFailure;
        }

        // WHEN COMMAND *NOT* IN "QUERY MODE" (I.E. "CREATION MODE"), CREATE AND
        // CONNECT A "closestPointOnCurve" NODE AND RETURN ITS NODE NAME:
        if (!queryFlagSet)
        {
            // CREATE THE NODE:
            MFnDependencyNode depNodeFn;
            if (closestPointOnCurveNodeName == "")
                depNodeFn.create("closestPointOnCurve");
            else
                depNodeFn.create("closestPointOnCurve", closestPointOnCurveNodeName);
            closestPointOnCurveNodeName = depNodeFn.name();

            // SET THE ".inPosition" ATTRIBUTE, IF SPECIFIED IN THE COMMAND:
            if (inPositionFlagSet)
            {
                MPlug inPositionXPlug = depNodeFn.findPlug("inPositionX");
                inPositionXPlug.setValue(inPosition.x);
                MPlug inPositionYPlug = depNodeFn.findPlug("inPositionY");
                inPositionYPlug.setValue(inPosition.y);
                MPlug inPositionZPlug = depNodeFn.findPlug("inPositionZ");
                inPositionZPlug.setValue(inPosition.z);
            }

            // MAKE SOME ADJUSTMENTS WHEN THE SPECIFIED NODE IS A
            // "TRANSFORM" OF A CURVE SHAPE:
            unsigned instanceNumber=0;
            if (curveDagPath.node().hasFn(MFn::kTransform))
            {
                // EXTEND THE DAGPATH TO ITS CURVE "SHAPE" NODE:
                curveDagPath.extendToShape();

                // TRANSFORMS ARE *NOT* NECESSARILY THE "FIRST" INSTANCE
                // TRANSFORM OF A CURVE SHAPE:
                instanceNumber = curveDagPath.instanceNumber();
            }

           // CONNECT THE NODES:
           MPlug worldCurvePlug, inCurvePlug;
           inCurvePlug = depNodeFn.findPlug("inCurve");
           depNodeFn.setObject(curveDagPath.node());
           worldCurvePlug = depNodeFn.findPlug("worldSpace");
           worldCurvePlug =    worldCurvePlug.elementByLogicalIndex(instanceNumber);
           MDGModifier dgModifier;
           dgModifier.connect(worldCurvePlug, inCurvePlug);
           dgModifier.doIt();

           // SET COMMAND RESULT TO BE NEW NODE'S NAME, AND RETURN:
           setResult(closestPointOnCurveNodeName);
           return MStatus::kSuccess;
        }

        // OTHERWISE, WE'RE IN THE COMMAND'S "QUERY MODE":
        else
        {

            // COMPUTE THE CLOSEST POSITION, NORMAL, TANGENT, PARAMETER-U
            // AND DISTANCE, USING THE *FIRST* INSTANCE TRANSFORM WHEN CURVE
            // IS SPECIFIED AS A "SHAPE":
            MPoint position;
            MVector normal, tangent;
            double paramU, distance;
            closestTangentUAndDistance(curveDagPath, inPosition, position, normal, tangent, paramU, distance);
            // WHEN NO QUERYABLE FLAG IS SPECIFIED, INDICATE AN ERROR:
            if (!positionFlagSet && !normalFlagSet && !tangentFlagSet && !paramUFlagSet && !distanceFlagSet)
            {
                MStatus stat;
                MString msg = MStringResource::getString(kNoQueryFlag, stat);
                displayError(msg);
                return MStatus::kFailure;
            }

            // WHEN JUST THE "DISTANCE" IS QUERIED, RETURN A SINGLE
            // "FLOAT" INSTEAD OF AN ENTIRE FLOAT ARRAY FROM THE COMMAND:
            else if (distanceFlagSet && !(positionFlagSet || normalFlagSet ||       tangentFlagSet || paramUFlagSet))
            setResult(distance);
            // WHEN JUST THE "PARAMETER-U" IS QUERIED, RETURN A
            // SINGLE "FLOAT" INSTEAD OF AN ENTIRE FLOAT ARRAY FROM THE      COMMAND:
            else if (paramUFlagSet && !(positionFlagSet || normalFlagSet || tangentFlagSet || distanceFlagSet))
            setResult(paramU);

            // OTHERWISE, SET THE RETURN VALUE OF THE COMMAND'S RESULT TO
            // A "COMPOSITE ARRAY OF FLOATS":
            else
            {
                // HOLDS FLOAT ARRAY RESULT:
                MDoubleArray floatArrayResult;
                // APPEND THE RESULTS OF THE CLOSEST POSITION, NORMAL,
                // TANGENT, PARAMETER-U AND DISTANCE VALUES TO THE FLOAT ARRAY RESULT:
                if (positionFlagSet)
                {
                    floatArrayResult.append(position.x);
                    floatArrayResult.append(position.y);
                    floatArrayResult.append(position.z);
                }
                if (normalFlagSet)
                {
                    floatArrayResult.append(normal.x);
                    floatArrayResult.append(normal.y);
                    floatArrayResult.append(normal.z);
                }
                if (tangentFlagSet)
                {
                    floatArrayResult.append(tangent.x);
                    floatArrayResult.append(tangent.y);
                    floatArrayResult.append(tangent.z);
                }
                if (paramUFlagSet)
                    floatArrayResult.append(paramU);
                if (distanceFlagSet)
                    floatArrayResult.append(distance);

                // FINALLY, SET THE COMMAND'S RESULT:
                setResult(floatArrayResult);
            }
            return MStatus::kSuccess;
        }
    }

## closestPointOnCurveCmdInitStrings.mel

This file is the string initialization script referenced in the call to MFnPlugin::registerStringResources. It has a dual purpose:

1. It registers any string resources used by the plug-in MEL scripts. The strings registered in this example are used in AEclosestPointOnCurveTemplate.mel.
2. It calls loadPluginLanguageResources with the name of the file containing the localized string values for this plug-in. In this example, a file named closestPointOnCurve.pres.mel will be loaded if it is located in the appropriate language-specific location expected by loadPluginLanguageResources.

    // FILE: closestPointOnCurveInitStrings.mel
    // DESCRIPTION: Register script resources and load localized
    // resources for the "closestPointOnCurve" plugin
    global proc closestPointOnCurveInitStrings()
    {
        // Register script resources
        registerPluginResource("closestPointOnCurve", "kAETitle", "Closest Point On Curve Attributes");
        registerPluginResource("closestPointOnCurve", "kInputCurve", "Input Curve");
        registerPluginResource("closestPointOnCurve", "kResults", "Results");
        // Load any localized resources
        loadPluginLanguageResources("closestPointOnCurve", "closestPointOnCurve.pres.mel");

    }

## AEclosestPointOnCurveTemplate.mel

This file defines the custom attribute editor setup for the closestPointOnCurve node which is created by this plug-in. The code was modified to use string resources in place of hard-coded strings for the attribute editor labels. The attribute names themselves can also be displayed in translated form, but the programmer does not need to designate string resources manually for each attribute. All attributes defined by the plug-in will have attributeNiceName resources automatically generated in the extracted resource file.

    // FILE: AEclosestPointOnCurveTemplate.mel
    global proc AEclosestPointOnCurveTemplate(string $nodeName)
    {
        string $titleStr = getPluginResource("closestPointOnCurve", "kAETitle");
        string $inputCurveLabel = getPluginResource("closestPointOnCurve", "kInputCurve");
        string $resultLabel = getPluginResource("closestPointOnCurve", "kResults");
        editorTemplate -beginScrollLayout;
        editorTemplate -beginLayout $titleStr -collapse 0;
        editorTemplate -callCustom ( "AEinputNew \""+  $inputCurveLabel +"\"" ) ( "AEinputReplace \"" +
                        $inputCurveLabel + "\"" ) "inCurve";
        editorTemplate -addControl "inPosition";
        editorTemplate -beginLayout $resultLabel;
        editorTemplate -addControl "position";
        editorTemplate -addControl "normal";
        editorTemplate -addControl "tangent";
        editorTemplate -addControl "paramU";
        editorTemplate -addControl "distance";
        editorTemplate -endLayout;
        editorTemplate -endLayout;
        editorTemplate -suppress "inCurve";
        AEabstractBaseCreateTemplate $nodeName;
        editorTemplate -addExtraControls;
        editorTemplate -endScrollLayout;
    }

## closestPointOnCurve.pres.mel

This file contains the extracted string resources for the closestPointOnCurve plug-in. It was generated using the utility script pluginResourceUtil. All registered strings from the C++ code and MEL scripts are extracted to the file along with their default values. Also included are node and attribute nice name values for each node registered by the plug-in, which are generated automatically by the utility (there is no manual registration required for these node and attribute resources). It is important to note that this original extracted file containing the default values is not required by the plug-in when it is running in English. All string resources have their default value built directly into the plug-in itself. The extracted file is used as the master list of resources that are to be translated into other languages. When the translated versions are available, the files are placed in the appropriate language-specific resources sub-directory along the MAYA_PLUG_IN_RESOURCE_PATH.

    // File closestPointOnCurve.pres.mel
    // Resources for Plug-in: closestPointOnCurve
    //
    // ----------------------------
    // Registered string resources:
    // ----------------------------
    setPluginResource( "closestPointOnCurve", "kAETitle", "Closest Point On Curve Attributes");
    setPluginResource( "closestPointOnCurve", "kInputCurve", "Input Curve");
    setPluginResource( "closestPointOnCurve", "kInvalidType", "Object ^1s has invalid type.  Only a curve or its transform can be specified.");
    setPluginResource( "closestPointOnCurve", "kNoQueryFlag", "You must specify AT LEAST ONE queryable flag in query mode.  Use the `help` command to list all available flags.");
    setPluginResource( "closestPointOnCurve", "kNoValidObject", "A curve or its transform node must be specified as a command argument, or using your current selection.");
    setPluginResource( "closestPointOnCurve", "kResults", "Results");

    //
    // --------------------------
    // Registered node resources:
    // --------------------------
    //
    // Node: closestPointOnCurve
    //
    setNodeNiceNameResource( "closestPointOnCurve", "Closest Point On Curve" );
    setAttrNiceNameResource( "closestPointOnCurve", "ic", "In Curve" );
    setAttrNiceNameResource( "closestPointOnCurve", "ip", "In Position" );
    setAttrNiceNameResource( "closestPointOnCurve", "ipx", "In Position X" );
    setAttrNiceNameResource( "closestPointOnCurve", "ipy", "In Position Y" );
    setAttrNiceNameResource( "closestPointOnCurve", "ipz", "In Position Z" );
    setAttrNiceNameResource( "closestPointOnCurve", "p", "Position" );
    setAttrNiceNameResource( "closestPointOnCurve", "px", "Position X" );
    setAttrNiceNameResource( "closestPointOnCurve", "py", "Position Y" );
    setAttrNiceNameResource( "closestPointOnCurve", "pz", "Position Z" );
    setAttrNiceNameResource( "closestPointOnCurve", "n", "Normal" );
    setAttrNiceNameResource( "closestPointOnCurve", "nx", "Normal X" );
    setAttrNiceNameResource( "closestPointOnCurve", "ny", "Normal Y" );
    setAttrNiceNameResource( "closestPointOnCurve", "nz", "Normal Z" );
    setAttrNiceNameResource( "closestPointOnCurve", "t", "Tangent" );
    setAttrNiceNameResource( "closestPointOnCurve", "tx", "Tangent X" );
    setAttrNiceNameResource( "closestPointOnCurve", "ty", "Tangent Y" );
    setAttrNiceNameResource( "closestPointOnCurve", "tz", "Tangent Z" );
    setAttrNiceNameResource( "closestPointOnCurve", "u", "Param U" );
    setAttrNiceNameResource( "closestPointOnCurve", "d", "Distance" );